Merge "Revert "Default installations to using RemexHtml for tidying""
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Fri, 6 Apr 2018 14:02:11 +0000 (14:02 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Fri, 6 Apr 2018 14:02:11 +0000 (14:02 +0000)
47 files changed:
RELEASE-NOTES-1.31
docs/hooks.txt
includes/api/i18n/de.json
includes/api/i18n/es.json
includes/api/i18n/fr.json
includes/api/i18n/gl.json
includes/api/i18n/he.json
includes/api/i18n/hu.json
includes/api/i18n/ja.json
includes/api/i18n/lt.json
includes/api/i18n/pt-br.json
includes/api/i18n/pt.json
includes/api/i18n/ru.json
includes/api/i18n/uk.json
includes/api/i18n/zh-hans.json
includes/db/MWLBFactory.php
includes/libs/rdbms/database/Database.php
includes/libs/rdbms/database/DatabasePostgres.php
includes/libs/rdbms/database/utils/SavepointPostgres.php
includes/libs/rdbms/field/PostgresField.php
includes/libs/rdbms/lbfactory/ILBFactory.php
includes/libs/rdbms/lbfactory/LBFactory.php
includes/libs/rdbms/loadbalancer/ILoadBalancer.php
includes/libs/rdbms/loadbalancer/LoadBalancer.php
languages/i18n/ar.json
languages/i18n/be-tarask.json
languages/i18n/bg.json
languages/i18n/ce.json
languages/i18n/cs.json
languages/i18n/de.json
languages/i18n/frr.json
languages/i18n/gcr.json
languages/i18n/he.json
languages/i18n/hy.json
languages/i18n/it.json
languages/i18n/ml.json
languages/i18n/sr-ec.json
languages/i18n/th.json
maintenance/cleanupPreferences.php
maintenance/postgres/archives/patch-ts2pagetitle.sql
maintenance/postgres/tables.sql
tests/phpunit/MediaWikiTestCase.php
tests/phpunit/includes/Storage/RevisionStoreDbTest.php
tests/phpunit/includes/db/DatabasePostgresTest.php [new file with mode: 0644]
tests/phpunit/includes/db/DatabaseTestHelper.php
tests/phpunit/includes/libs/SamplingStatsdClientTest.php
tests/phpunit/includes/libs/rdbms/database/DatabaseSQLTest.php

index 23afa4f..029eea5 100644 (file)
@@ -331,6 +331,7 @@ changes to languages because of Phabricator reports.
   can use MediaWikiTitleCodec::getTitleInvalidRegex() instead.
 * HTMLForm & VFormHTMLForm::isVForm(), deprecated in 1.25, have been removed.
 * The ProfileSection class, deprecated in 1.25 and unused, has been removed.
+* Wikimedia\Rdbms\SavepointPostgres is deprecated.
 
 == Compatibility ==
 MediaWiki 1.31 requires PHP 5.5.9 or later. Although HHVM 3.18.5 or later is supported,
index 4e8474b..f35d610 100644 (file)
@@ -1209,6 +1209,14 @@ $row: the DB row for this line
   Currently only data attributes reserved to MediaWiki are allowed
   (see Sanitizer::isReservedDataAttribute).
 
+'DeleteUnknownPreferences': Called by the cleanupPreferences.php maintenance script to build a WHERE clause with which
+to delete preferences that are not known about. This hook is used by extensions that have dynamically-named preferences
+that should not be deleted in the usual cleanup process. For example, the Gadgets extension creates preferences prefixed
+with 'gadget-', and so anything with that prefix is excluded from the deletion.
+&where: An array that will be passed as the $cond parameter to IDatabase::select() to determine what will be deleted
+  from the user_properties table.
+$db: The IDatabase object, useful for accessing $db->buildLike() etc.
+
 'DifferenceEngineAfterLoadNewText': called in DifferenceEngine::loadNewText()
 after the new revision's content has been loaded into the class member variable
 $differenceEngine->mNewContent but before returning true from this function.
index fb3d258..997888d 100644 (file)
        "apihelp-query+exturlusage-param-namespace": "Die aufzulistenden Seiten-Namensräume.",
        "apihelp-query+exturlusage-param-limit": "Wie viele Seiten zurückgegeben werden sollen.",
        "apihelp-query+exturlusage-param-expandurl": "Expandiert protokollrelative URLs mit dem kanonischen Protokoll.",
-       "apihelp-query+exturlusage-example-simple": "Zeigt Seiten, die auf <kbd>http://www.mediawiki.org</kbd> verlinken.",
+       "apihelp-query+exturlusage-example-simple": "Zeigt Seiten, die auf <kbd>https://www.mediawiki.org</kbd> verlinken.",
        "apihelp-query+filearchive-summary": "Alle gelöschten Dateien der Reihe nach auflisten.",
        "apihelp-query+filearchive-param-from": "Der Bildertitel, bei dem die Auflistung beginnen soll.",
        "apihelp-query+filearchive-param-to": "Der Bildertitel, bei dem die Auflistung enden soll.",
index 4733de4..4e5fb5e 100644 (file)
        "apihelp-query+exturlusage-param-namespace": "Los espacios de nombres que enumerar.",
        "apihelp-query+exturlusage-param-limit": "Cuántas páginas se devolverán.",
        "apihelp-query+exturlusage-param-expandurl": "Expandir las URL relativas a un protocolo con el protocolo canónico.",
-       "apihelp-query+exturlusage-example-simple": "Mostrar páginas que enlacen con <kbd>http://www.mediawiki.org</kbd>.",
+       "apihelp-query+exturlusage-example-simple": "Mostrar páginas que enlacen con <kbd>https://www.mediawiki.org</kbd>.",
        "apihelp-query+filearchive-summary": "Enumerar todos los archivos borrados de forma secuencial.",
        "apihelp-query+filearchive-param-from": "El título de imagen para comenzar la enumeración",
        "apihelp-query+filearchive-param-to": "El título de imagen para detener la enumeración.",
index f4820bf..912bf2b 100644 (file)
        "apihelp-query+exturlusage-param-namespace": "Les espaces de nom à énumérer.",
        "apihelp-query+exturlusage-param-limit": "Combien de pages renvoyer.",
        "apihelp-query+exturlusage-param-expandurl": "Étendre les URLs relatives au protocole avec le protocole canonique.",
-       "apihelp-query+exturlusage-example-simple": "Afficher les pages avec un lien vers <kbd>http://www.mediawiki.org</kbd>.",
+       "apihelp-query+exturlusage-example-simple": "Afficher les pages avec un lien vers <kbd>https://www.mediawiki.org</kbd>.",
        "apihelp-query+filearchive-summary": "Énumérer séquentiellement tous les fichiers supprimés.",
        "apihelp-query+filearchive-param-from": "Le titre de l’image auquel démarrer l’énumération.",
        "apihelp-query+filearchive-param-to": "Le titre de l’image auquel arrêter l’énumération.",
index 4c66794..5ec9bae 100644 (file)
        "apihelp-query+exturlusage-param-namespace": "Espazo de nomes a enumerar.",
        "apihelp-query+exturlusage-param-limit": "Cantas páxinas devolver.",
        "apihelp-query+exturlusage-param-expandurl": "Expandir as URLs relativas a un protocolo co protocolo canónico.",
-       "apihelp-query+exturlusage-example-simple": "Mostrar páxinas ligando a <kbd>http://www.mediawiki.org</kbd>.",
+       "apihelp-query+exturlusage-example-simple": "Mostrar páxinas ligando a <kbd>https://www.mediawiki.org</kbd>.",
        "apihelp-query+filearchive-summary": "Enumerar secuencialmente todos os ficheiros borrados.",
        "apihelp-query+filearchive-param-from": "Título da imaxe coa que comezar a enumeración.",
        "apihelp-query+filearchive-param-to": "Título da imaxe coa que rematar a enumeración.",
index 2064b7b..f6a201b 100644 (file)
        "apihelp-query+exturlusage-param-namespace": "איזה מרחב שם למנות.",
        "apihelp-query+exturlusage-param-limit": "כמה דפים להחזיר.",
        "apihelp-query+exturlusage-param-expandurl": "הרחבת URL־ים בעלי פרוטוקול יחסי בפרוטוקול קנוני.",
-       "apihelp-query+exturlusage-example-simple": "הצגת דפים שמקשרים ל־<kbd>http://www.mediawiki.org</kbd>.",
+       "apihelp-query+exturlusage-example-simple": "הצגת דפים שמקשרים ל־<kbd>https://www.mediawiki.org</kbd>.",
        "apihelp-query+filearchive-summary": "למנות את כל הקבצים המחוקים לפי הסדר.",
        "apihelp-query+filearchive-param-from": "מאיזו כותרת תמונה להתחיל למנות.",
        "apihelp-query+filearchive-param-to": "באיזו כותרת תמונה להפסיק למנות.",
        "apihelp-query+recentchanges-paramvalue-prop-sizes": "הוספת אורך הדף החדש והישן בבייטים.",
        "apihelp-query+recentchanges-paramvalue-prop-redirect": "מתייג שהדף הוא הפניה.",
        "apihelp-query+recentchanges-paramvalue-prop-patrolled": "מתייג עריכה בת־בדיקה בתור בדוקה או בלתי־בדוקה.",
+       "apihelp-query+recentchanges-paramvalue-prop-autopatrolled": "ציון האם עריכות הניתנות לבדיקה נבדקו אוטומטית או לא.",
        "apihelp-query+recentchanges-paramvalue-prop-loginfo": "הוספת מידע יומן (זהה יומן, סוג יומן וכו') לעיולי יומן.",
        "apihelp-query+recentchanges-paramvalue-prop-tags": "רשימת תגים עבור העיול.",
        "apihelp-query+recentchanges-paramvalue-prop-sha1": "הוספת סיכום־ביקורת תוכן לעיולים שמשויכים לגרסה.",
        "apihelp-query+usercontribs-paramvalue-prop-sizediff": "הוספת ההפרש של העריכה אל מול ההורה שלה.",
        "apihelp-query+usercontribs-paramvalue-prop-flags": "הוספת הדגלים של העריכה.",
        "apihelp-query+usercontribs-paramvalue-prop-patrolled": "מתייג עריכות בדוקות.",
+       "apihelp-query+usercontribs-paramvalue-prop-autopatrolled": "תיוג עריכות שנבדקו אוטומטית.",
        "apihelp-query+usercontribs-paramvalue-prop-tags": "רשימת תגים עבור עריכות.",
        "apihelp-query+usercontribs-param-show": "הצגה רק של פריטים שמתאימים לאמות המידה האלה, למשל רק עריכות לא־משניות.\n\nאם מוגדר <kbd>$2show=patrolled</kbd> או <kbd>$2show=!patrolled</kbd>, גרסאות ישנות מ־<var dir=\"ltr\">[[mw:Special:MyLanguage/Manual:$wgRCMaxAge|$wgRCMaxAge]]</var>‏ ({{PLURAL:$1|שנייה אחת|$1 שניות}}) לא תוצגנה.",
        "apihelp-query+usercontribs-param-tag": "לרשום רק גרסאות עם התג הזה.",
index f6f813d..4451f19 100644 (file)
        "apihelp-query+exturlusage-param-protocol": "Az URL protokollja. Ha üres és az <var>$1query</var> paraméter meg van adva, a protokoll <kbd>http</kbd>. Hagyd ezt és az <var>$1query</var> paramétert is üresen az összes külső link listázásához.",
        "apihelp-query+exturlusage-param-namespace": "A listázandó névtér.",
        "apihelp-query+exturlusage-param-limit": "A visszaadandó lapok száma.",
-       "apihelp-query+exturlusage-example-simple": "A <kbd>http://www.mediawiki.org</kbd> URL-re hivatkozó lapok megjelenítése.",
+       "apihelp-query+exturlusage-example-simple": "A <kbd>https://www.mediawiki.org</kbd> URL-re hivatkozó lapok megjelenítése.",
        "apihelp-query+filearchive-summary": "Az összes törölt fájl visszaadása.",
        "apihelp-query+filearchive-param-from": "A fájlok listázása ettől a címtől.",
        "apihelp-query+filearchive-param-to": "A fájlok listázása eddig a címig.",
index 3145523..6a14bb5 100644 (file)
        "apihelp-query+exturlusage-param-query": "プロトコルを除いた検索文字列。[[Special:LinkSearch]] も参照してください。すべての外部リンクを一覧表示するには空欄にしてください。",
        "apihelp-query+exturlusage-param-namespace": "列挙するページ名前空間。",
        "apihelp-query+exturlusage-param-limit": "返すページの数。",
-       "apihelp-query+exturlusage-example-simple": "<kbd>http://www.mediawiki.org</kbd> にリンクしているページを一覧表示する。",
+       "apihelp-query+exturlusage-example-simple": "<kbd>https://www.mediawiki.org</kbd> にリンクしているページを一覧表示する。",
        "apihelp-query+filearchive-summary": "削除されたファイルをすべて順に列挙します。",
        "apihelp-query+filearchive-param-from": "列挙の始点となる画像のページ名。",
        "apihelp-query+filearchive-param-to": "列挙の終点となる画像のページ名。",
index 6f2a72c..f470a72 100644 (file)
        "apihelp-query+exturlusage-paramvalue-prop-ids": "Prideda puslapio ID.",
        "apihelp-query+exturlusage-paramvalue-prop-url": "Prideda URL, panaudota puslapyje.",
        "apihelp-query+exturlusage-param-limit": "Kiek puslapių gražinti.",
-       "apihelp-query+exturlusage-example-simple": "Rodyti puslapius, nurodančius į <kbd>http://www.mediawiki.org</kbd>.",
+       "apihelp-query+exturlusage-example-simple": "Rodyti puslapius, nurodančius į <kbd>https://www.mediawiki.org</kbd>.",
        "apihelp-query+filearchive-param-prop": "Kokią paveikslėlio informaciją gauti:",
        "apihelp-query+filearchive-paramvalue-prop-timestamp": "Prideda laiko žymę įkeltai versijai.",
        "apihelp-query+filearchive-paramvalue-prop-user": "Prideda vartotoją, kuris įkėlė paveikslėlio versiją.",
index 24a558b..69c78d3 100644 (file)
        "apihelp-query+exturlusage-param-namespace": "O espaço nominal das páginas para enumerar.",
        "apihelp-query+exturlusage-param-limit": "Quantas páginas retornar.",
        "apihelp-query+exturlusage-param-expandurl": "Expandir URLs relativos ao protocolo com o protocolo canônico.",
-       "apihelp-query+exturlusage-example-simple": "Mostra páginas vigiadas à <kbd>http://www.mediawiki.org</kbd>.",
+       "apihelp-query+exturlusage-example-simple": "Mostra páginas vigiadas à <kbd>https://www.mediawiki.org</kbd>.",
        "apihelp-query+filearchive-summary": "Enumerar todos os arquivos excluídos sequencialmente.",
        "apihelp-query+filearchive-param-from": "O título da imagem do qual começar a enumeração.",
        "apihelp-query+filearchive-param-to": "O título da imagem no qual parar a enumeração.",
index 807825d..363d5a7 100644 (file)
        "apihelp-query+exturlusage-param-namespace": "Os espaços nominais a serem enumerados.",
        "apihelp-query+exturlusage-param-limit": "O número de páginas a serem devolvidas.",
        "apihelp-query+exturlusage-param-expandurl": "Expandir os URL relativos a protocolo com o protocolo canónico.",
-       "apihelp-query+exturlusage-example-simple": "Mostrar as páginas com hiperligações para <kbd>http://www.mediawiki.org</kbd>.",
+       "apihelp-query+exturlusage-example-simple": "Mostrar as páginas com hiperligações para <kbd>https://www.mediawiki.org</kbd>.",
        "apihelp-query+filearchive-summary": "Enumerar todos os ficheiros eliminados sequencialmente.",
        "apihelp-query+filearchive-param-from": "O título da imagem a partir do qual será começada a enumeração.",
        "apihelp-query+filearchive-param-to": "O título da imagem no qual será terminada a enumeração.",
index ae99794..816c95f 100644 (file)
        "apihelp-query+exturlusage-param-namespace": "Пространства имён для перечисления.",
        "apihelp-query+exturlusage-param-limit": "Сколько страниц вернуть.",
        "apihelp-query+exturlusage-param-expandurl": "Раскрыть зависимые от протокола ссылки с какноничным протоколом.",
-       "apihelp-query+exturlusage-example-simple": "Показать страницы, ссылающиеся на <kbd>http://www.mediawiki.org</kbd>.",
+       "apihelp-query+exturlusage-example-simple": "Показать страницы, ссылающиеся на <kbd>https://www.mediawiki.org</kbd>.",
        "apihelp-query+filearchive-summary": "Перечисление всех удалённых файлов.",
        "apihelp-query+filearchive-param-from": "Название изображения, с которого начать перечисление.",
        "apihelp-query+filearchive-param-to": "Название изображения, на котором закончить перечисление.",
index f55f65e..8efeb37 100644 (file)
        "apihelp-query+exturlusage-param-namespace": "Простори назв для переліку.",
        "apihelp-query+exturlusage-param-limit": "Скільки сторінок виводити.",
        "apihelp-query+exturlusage-param-expandurl": "Розгорнути протокол-залежні URL за канонічним протоколом.",
-       "apihelp-query+exturlusage-example-simple": "Показати сторінки, які посилаються на <kbd>http://www.mediawiki.org</kbd>.",
+       "apihelp-query+exturlusage-example-simple": "Показати сторінки, які посилаються на <kbd>https://www.mediawiki.org</kbd>.",
        "apihelp-query+filearchive-summary": "Перерахувати всі вилучені файли послідовно.",
        "apihelp-query+filearchive-param-from": "Назва зображення, з якої почати перелічувати.",
        "apihelp-query+filearchive-param-to": "Назва зображення, якою закінчити перелічувати.",
index 2a8eb04..ee5aa5a 100644 (file)
        "apihelp-query+exturlusage-param-namespace": "要列举的页面名字空间。",
        "apihelp-query+exturlusage-param-limit": "返回多少页面。",
        "apihelp-query+exturlusage-param-expandurl": "用标准协议展开协议相关URL。",
-       "apihelp-query+exturlusage-example-simple": "显示链接至<kbd>http://www.mediawiki.org</kbd>的页面。",
+       "apihelp-query+exturlusage-example-simple": "显示链接至<kbd>https://www.mediawiki.org</kbd>的页面。",
        "apihelp-query+filearchive-summary": "循序列举所有被删除的文件。",
        "apihelp-query+filearchive-param-from": "枚举的起始图片标题。",
        "apihelp-query+filearchive-param-to": "枚举的结束图片标题。",
index f0a17f7..82d9c1d 100644 (file)
@@ -31,6 +31,10 @@ use Wikimedia\Rdbms\DatabaseDomain;
  * @ingroup Database
  */
 abstract class MWLBFactory {
+
+       /** @var array Cache of already-logged deprecation messages */
+       private static $loggedDeprecations = [];
+
        /**
         * @param array $lbConf Config for LBFactory::__construct()
         * @param Config $mainConfig Main config object from MediaWikiServices
@@ -57,6 +61,7 @@ abstract class MWLBFactory {
                        'connLogger' => LoggerFactory::getInstance( 'DBConnection' ),
                        'perfLogger' => LoggerFactory::getInstance( 'DBPerformance' ),
                        'errorLogger' => [ MWExceptionHandler::class, 'logException' ],
+                       'deprecationLogger' => [ static::class, 'logDeprecation' ],
                        'cliMode' => $wgCommandLineMode,
                        'hostname' => wfHostname(),
                        'readOnlyReason' => $readOnlyMode->getReason(),
@@ -228,4 +233,22 @@ abstract class MWLBFactory {
                        ] );
                }
        }
+
+       /**
+        * Log a database deprecation warning
+        * @param string $msg Deprecation message
+        */
+       public static function logDeprecation( $msg ) {
+               global $wgDevelopmentWarnings;
+
+               if ( isset( self::$loggedDeprecations[$msg] ) ) {
+                       return;
+               }
+               self::$loggedDeprecations[$msg] = true;
+
+               if ( $wgDevelopmentWarnings ) {
+                       trigger_error( $msg, E_USER_DEPRECATED );
+               }
+               wfDebugLog( 'deprecated', $msg, 'private' );
+       }
 }
index b395711..5336b25 100644 (file)
@@ -101,6 +101,8 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
        protected $queryLogger;
        /** @var callback Error logging callback */
        protected $errorLogger;
+       /** @var callback Deprecation logging callback */
+       protected $deprecationLogger;
 
        /** @var resource|null Database connection */
        protected $conn = null;
@@ -312,6 +314,7 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
                $this->connLogger = $params['connLogger'];
                $this->queryLogger = $params['queryLogger'];
                $this->errorLogger = $params['errorLogger'];
+               $this->deprecationLogger = $params['deprecationLogger'];
 
                if ( isset( $params['nonNativeInsertSelectBatchSize'] ) ) {
                        $this->nonNativeInsertSelectBatchSize = $params['nonNativeInsertSelectBatchSize'];
@@ -396,6 +399,7 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
         *      includes the agent as a SQL comment.
         *   - trxProfiler: Optional TransactionProfiler instance.
         *   - errorLogger: Optional callback that takes an Exception and logs it.
+        *   - deprecationLogger: Optional callback that takes a string and logs it.
         *   - cliMode: Whether to consider the execution context that of a CLI script.
         *   - agent: Optional name used to identify the end-user in query profiling/logging.
         *   - srvCache: Optional BagOStuff instance to an APC-style cache.
@@ -437,6 +441,11 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
                                        trigger_error( get_class( $e ) . ': ' . $e->getMessage(), E_USER_WARNING );
                                };
                        }
+                       if ( !isset( $p['deprecationLogger'] ) ) {
+                               $p['deprecationLogger'] = function ( $msg ) {
+                                       trigger_error( $msg, E_USER_DEPRECATED );
+                               };
+                       }
 
                        /** @var Database $conn */
                        $conn = new $class( $p );
@@ -1145,17 +1154,14 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
                                        # In the first case, the only options going forward are (a) ROLLBACK, or
                                        # (b) ROLLBACK TO SAVEPOINT (if one was set). If the later case, the only
                                        # option is ROLLBACK, since the snapshots would have been released.
-                                       if ( is_object( $tempIgnore ) ) {
-                                               // Ugly hack to know that savepoints are in use for postgres
-                                               // FIXME: remove this and make DatabasePostgres use ATOMIC_CANCELABLE
-                                       } else {
-                                               $this->trxStatus = self::STATUS_TRX_ERROR;
-                                               $this->trxStatusCause =
-                                                       $this->makeQueryException( $lastError, $lastErrno, $sql, $fname );
-                                               $tempIgnore = false; // cannot recover
-                                       }
+                                       $this->trxStatus = self::STATUS_TRX_ERROR;
+                                       $this->trxStatusCause =
+                                               $this->makeQueryException( $lastError, $lastErrno, $sql, $fname );
+                                       $tempIgnore = false; // cannot recover
                                } else {
-                                       # Nothing prior was there to lose from the transaction
+                                       # Nothing prior was there to lose from the transaction,
+                                       # so just roll it back.
+                                       $this->doRollback( __METHOD__ . " ($fname)" );
                                        $this->trxStatus = self::STATUS_TRX_OK;
                                }
                        }
@@ -1318,7 +1324,7 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware
        private function handleSessionLoss() {
                // Clean up tracking of session-level things...
                // https://dev.mysql.com/doc/refman/5.7/en/implicit-commit.html
-               // https://www.postgresql.org/docs/9.1/static/sql-createtable.html (ignoring ON COMMIT)
+               // https://www.postgresql.org/docs/9.2/static/sql-createtable.html (ignoring ON COMMIT)
                $this->sessionTempTables = [];
                // https://dev.mysql.com/doc/refman/5.7/en/miscellaneous-functions.html#function_get-lock
                // https://www.postgresql.org/docs/9.4/static/functions-admin.html#FUNCTIONS-ADVISORY-LOCKS
index 9ffcc8b..525d308 100644 (file)
@@ -36,8 +36,6 @@ class DatabasePostgres extends Database {
 
        /** @var resource */
        protected $lastResultHandle = null;
-       /** @var int The number of rows affected as an integer */
-       protected $lastAffectedRowCount = null;
 
        /** @var float|string */
        private $numericVersion = null;
@@ -45,6 +43,8 @@ class DatabasePostgres extends Database {
        private $connectString;
        /** @var string */
        private $coreSchema;
+       /** @var string */
+       private $tempSchema;
        /** @var string[] Map of (reserved table name => alternate table name) */
        private $keywordTableMap = [];
 
@@ -75,15 +75,17 @@ class DatabasePostgres extends Database {
        }
 
        public function hasConstraint( $name ) {
-               $conn = $this->getBindingHandle();
-
-               $sql = "SELECT 1 FROM pg_catalog.pg_constraint c, pg_catalog.pg_namespace n " .
-                       "WHERE c.connamespace = n.oid AND conname = '" .
-                       pg_escape_string( $conn, $name ) . "' AND n.nspname = '" .
-                       pg_escape_string( $conn, $this->getCoreSchema() ) . "'";
-               $res = $this->doQuery( $sql );
-
-               return $this->numRows( $res );
+               foreach ( $this->getCoreSchemas() as $schema ) {
+                       $sql = "SELECT 1 FROM pg_catalog.pg_constraint c, pg_catalog.pg_namespace n " .
+                               "WHERE c.connamespace = n.oid AND conname = " .
+                               $this->addQuotes( $name ) . " AND n.nspname = " .
+                               $this->addQuotes( $schema );
+                       $res = $this->doQuery( $sql );
+                       if ( $res && $this->numRows( $res ) ) {
+                               return true;
+                       }
+               }
+               return false;
        }
 
        public function open( $server, $user, $password, $dbName ) {
@@ -155,9 +157,7 @@ class DatabasePostgres extends Database {
                $this->query( "SET datestyle = 'ISO, YMD'", __METHOD__ );
                $this->query( "SET timezone = 'GMT'", __METHOD__ );
                $this->query( "SET standard_conforming_strings = on", __METHOD__ );
-               if ( $this->getServerVersion() >= 9.0 ) {
-                       $this->query( "SET bytea_output = 'escape'", __METHOD__ ); // PHP bug 53127
-               }
+               $this->query( "SET bytea_output = 'escape'", __METHOD__ ); // PHP bug 53127
 
                $this->determineCoreSchema( $this->schema );
                // The schema to be used is now in the search path; no need for explicit qualification
@@ -219,7 +219,6 @@ class DatabasePostgres extends Database {
                        throw new DBUnexpectedError( $this, "Unable to post new query to PostgreSQL\n" );
                }
                $this->lastResultHandle = pg_get_result( $conn );
-               $this->lastAffectedRowCount = null;
                if ( pg_result_error( $this->lastResultHandle ) ) {
                        return false;
                }
@@ -371,10 +370,6 @@ class DatabasePostgres extends Database {
        }
 
        protected function fetchAffectedRowCount() {
-               if ( !is_null( $this->lastAffectedRowCount ) ) {
-                       // Forced result for simulated queries
-                       return $this->lastAffectedRowCount;
-               }
                if ( !$this->lastResultHandle ) {
                        return 0;
                }
@@ -437,59 +432,65 @@ class DatabasePostgres extends Database {
 
        public function indexAttributes( $index, $schema = false ) {
                if ( $schema === false ) {
-                       $schema = $this->getCoreSchema();
-               }
-               /*
-                * A subquery would be not needed if we didn't care about the order
-                * of attributes, but we do
-                */
-               $sql = <<<__INDEXATTR__
-
-                       SELECT opcname,
-                               attname,
-                               i.indoption[s.g] as option,
-                               pg_am.amname
-                       FROM
-                               (SELECT generate_series(array_lower(isub.indkey,1), array_upper(isub.indkey,1)) AS g
-                                       FROM
-                                               pg_index isub
-                                       JOIN pg_class cis
-                                               ON cis.oid=isub.indexrelid
-                                       JOIN pg_namespace ns
-                                               ON cis.relnamespace = ns.oid
-                                       WHERE cis.relname='$index' AND ns.nspname='$schema') AS s,
-                               pg_attribute,
-                               pg_opclass opcls,
-                               pg_am,
-                               pg_class ci
-                               JOIN pg_index i
-                                       ON ci.oid=i.indexrelid
-                               JOIN pg_class ct
-                                       ON ct.oid = i.indrelid
-                               JOIN pg_namespace n
-                                       ON ci.relnamespace = n.oid
-                               WHERE
-                                       ci.relname='$index' AND n.nspname='$schema'
-                                       AND     attrelid = ct.oid
-                                       AND     i.indkey[s.g] = attnum
-                                       AND     i.indclass[s.g] = opcls.oid
-                                       AND     pg_am.oid = opcls.opcmethod
+                       $schemas = $this->getCoreSchemas();
+               } else {
+                       $schemas = [ $schema ];
+               }
+
+               $eindex = $this->addQuotes( $index );
+
+               foreach ( $schemas as $schema ) {
+                       $eschema = $this->addQuotes( $schema );
+                       /*
+                        * A subquery would be not needed if we didn't care about the order
+                        * of attributes, but we do
+                        */
+                       $sql = <<<__INDEXATTR__
+
+                               SELECT opcname,
+                                       attname,
+                                       i.indoption[s.g] as option,
+                                       pg_am.amname
+                               FROM
+                                       (SELECT generate_series(array_lower(isub.indkey,1), array_upper(isub.indkey,1)) AS g
+                                               FROM
+                                                       pg_index isub
+                                               JOIN pg_class cis
+                                                       ON cis.oid=isub.indexrelid
+                                               JOIN pg_namespace ns
+                                                       ON cis.relnamespace = ns.oid
+                                               WHERE cis.relname=$eindex AND ns.nspname=$eschema) AS s,
+                                       pg_attribute,
+                                       pg_opclass opcls,
+                                       pg_am,
+                                       pg_class ci
+                                       JOIN pg_index i
+                                               ON ci.oid=i.indexrelid
+                                       JOIN pg_class ct
+                                               ON ct.oid = i.indrelid
+                                       JOIN pg_namespace n
+                                               ON ci.relnamespace = n.oid
+                                       WHERE
+                                               ci.relname=$eindex AND n.nspname=$eschema
+                                               AND     attrelid = ct.oid
+                                               AND     i.indkey[s.g] = attnum
+                                               AND     i.indclass[s.g] = opcls.oid
+                                               AND     pg_am.oid = opcls.opcmethod
 __INDEXATTR__;
-               $res = $this->query( $sql, __METHOD__ );
-               $a = [];
-               if ( $res ) {
-                       foreach ( $res as $row ) {
-                               $a[] = [
-                                       $row->attname,
-                                       $row->opcname,
-                                       $row->amname,
-                                       $row->option ];
+                       $res = $this->query( $sql, __METHOD__ );
+                       $a = [];
+                       if ( $res ) {
+                               foreach ( $res as $row ) {
+                                       $a[] = [
+                                               $row->attname,
+                                               $row->opcname,
+                                               $row->amname,
+                                               $row->option ];
+                               }
+                               return $a;
                        }
-               } else {
-                       return null;
                }
-
-               return $a;
+               return null;
        }
 
        public function indexUnique( $table, $index, $fname = __METHOD__ ) {
@@ -559,18 +560,7 @@ __INDEXATTR__;
                return parent::selectSQLText( $table, $vars, $conds, $fname, $options, $join_conds );
        }
 
-       /**
-        * INSERT wrapper, inserts an array into a table
-        *
-        * $args may be a single associative array, or an array of these with numeric keys,
-        * for multi-row insert (Postgres version 8.2 and above only).
-        *
-        * @param string $table Name of the table to insert to.
-        * @param array $args Items to insert into the table.
-        * @param string $fname Name of the function, for profiling
-        * @param array|string $options String or array. Valid options: IGNORE
-        * @return bool Success of insert operation. IGNORE always returns true.
-        */
+       /** @inheritDoc */
        public function insert( $table, $args, $fname = __METHOD__, $options = [] ) {
                if ( !count( $args ) ) {
                        return true;
@@ -586,98 +576,68 @@ __INDEXATTR__;
                }
 
                if ( isset( $args[0] ) && is_array( $args[0] ) ) {
-                       $multi = true;
+                       $rows = $args;
                        $keys = array_keys( $args[0] );
                } else {
-                       $multi = false;
+                       $rows = [ $args ];
                        $keys = array_keys( $args );
                }
 
-               // If IGNORE is set, we use savepoints to emulate mysql's behavior
-               // @todo If PostgreSQL 9.5+, we could use ON CONFLICT DO NOTHING instead
-               $savepoint = $olde = null;
-               $numrowsinserted = 0;
-               if ( in_array( 'IGNORE', $options ) ) {
-                       $savepoint = new SavepointPostgres( $this, 'mw', $this->queryLogger );
-                       $olde = error_reporting( 0 );
-                       // For future use, we may want to track the number of actual inserts
-                       // Right now, insert (all writes) simply return true/false
-               }
+               $ignore = in_array( 'IGNORE', $options );
 
                $sql = "INSERT INTO $table (" . implode( ',', $keys ) . ') VALUES ';
 
-               if ( $multi ) {
-                       if ( $this->numericVersion >= 8.2 && !$savepoint ) {
-                               $first = true;
-                               foreach ( $args as $row ) {
-                                       if ( $first ) {
-                                               $first = false;
-                                       } else {
-                                               $sql .= ',';
-                                       }
-                                       $sql .= '(' . $this->makeList( $row ) . ')';
+               if ( $this->numericVersion >= 9.5 || !$ignore ) {
+                       // No IGNORE or our PG has "ON CONFLICT DO NOTHING"
+                       $first = true;
+                       foreach ( $rows as $row ) {
+                               if ( $first ) {
+                                       $first = false;
+                               } else {
+                                       $sql .= ',';
                                }
-                               $res = (bool)$this->query( $sql, $fname, $savepoint );
-                       } else {
-                               $res = true;
-                               $origsql = $sql;
-                               foreach ( $args as $row ) {
-                                       $tempsql = $origsql;
+                               $sql .= '(' . $this->makeList( $row ) . ')';
+                       }
+                       if ( $ignore ) {
+                               $sql .= ' ON CONFLICT DO NOTHING';
+                       }
+                       $this->query( $sql, $fname );
+               } else {
+                       // Emulate IGNORE by doing each row individually, with savepoints
+                       // to roll back as necessary.
+                       $numrowsinserted = 0;
+
+                       $tok = $this->startAtomic( "$fname (outer)", self::ATOMIC_CANCELABLE );
+                       try {
+                               foreach ( $rows as $row ) {
+                                       $tempsql = $sql;
                                        $tempsql .= '(' . $this->makeList( $row ) . ')';
 
-                                       if ( $savepoint ) {
-                                               $savepoint->savepoint();
-                                       }
-
-                                       $tempres = (bool)$this->query( $tempsql, $fname, $savepoint );
-
-                                       if ( $savepoint ) {
-                                               $bar = pg_result_error( $this->lastResultHandle );
-                                               if ( $bar != false ) {
-                                                       $savepoint->rollback();
-                                               } else {
-                                                       $savepoint->release();
-                                                       $numrowsinserted++;
+                                       $this->startAtomic( "$fname (inner)", self::ATOMIC_CANCELABLE );
+                                       try {
+                                               $this->query( $tempsql, $fname );
+                                               $this->endAtomic( "$fname (inner)" );
+                                               $numrowsinserted++;
+                                       } catch ( DBQueryError $e ) {
+                                               $this->cancelAtomic( "$fname (inner)" );
+                                               // Our IGNORE is supposed to ignore duplicate key errors, but not others.
+                                               // (even though MySQL's version apparently ignores all errors)
+                                               if ( $e->errno !== '23505' ) {
+                                                       throw $e;
                                                }
                                        }
-
-                                       // If any of them fail, we fail overall for this function call
-                                       // Note that this will be ignored if IGNORE is set
-                                       if ( !$tempres ) {
-                                               $res = false;
-                                       }
-                               }
-                       }
-               } else {
-                       // Not multi, just a lone insert
-                       if ( $savepoint ) {
-                               $savepoint->savepoint();
-                       }
-
-                       $sql .= '(' . $this->makeList( $args ) . ')';
-                       $res = (bool)$this->query( $sql, $fname, $savepoint );
-                       if ( $savepoint ) {
-                               $bar = pg_result_error( $this->lastResultHandle );
-                               if ( $bar != false ) {
-                                       $savepoint->rollback();
-                               } else {
-                                       $savepoint->release();
-                                       $numrowsinserted++;
                                }
+                       } catch ( Exception $e ) {
+                               $this->cancelAtomic( "$fname (outer)", $tok );
+                               throw $e;
                        }
-               }
-               if ( $savepoint ) {
-                       error_reporting( $olde );
-                       $savepoint->commit();
+                       $this->endAtomic( "$fname (outer)" );
 
                        // Set the affected row count for the whole operation
-                       $this->lastAffectedRowCount = $numrowsinserted;
-
-                       // IGNORE always returns true
-                       return true;
+                       $this->affectedRowCount = $numrowsinserted;
                }
 
-               return $res;
+               return true;
        }
 
        /**
@@ -707,14 +667,31 @@ __INDEXATTR__;
                        $insertOptions = [ $insertOptions ];
                }
 
-               /*
-                * If IGNORE is set, use the non-native version.
-                * @todo If PostgreSQL 9.5+, we could use ON CONFLICT DO NOTHING
-                */
                if ( in_array( 'IGNORE', $insertOptions ) ) {
-                       return $this->nonNativeInsertSelect(
-                               $destTable, $srcTable, $varMap, $conds, $fname, $insertOptions, $selectOptions, $selectJoinConds
-                       );
+                       if ( $this->getServerVersion() >= 9.5 ) {
+                               // Use ON CONFLICT DO NOTHING if we have it for IGNORE
+                               $destTable = $this->tableName( $destTable );
+
+                               $selectSql = $this->selectSQLText(
+                                       $srcTable,
+                                       array_values( $varMap ),
+                                       $conds,
+                                       $fname,
+                                       $selectOptions,
+                                       $selectJoinConds
+                               );
+
+                               $sql = "INSERT INTO $destTable (" . implode( ',', array_keys( $varMap ) ) . ') ' .
+                                       $selectSql . ' ON CONFLICT DO NOTHING';
+
+                               return $this->query( $sql, $fname );
+                       } else {
+                               // IGNORE and we don't have ON CONFLICT DO NOTHING, so just use the non-native version
+                               return $this->nonNativeInsertSelect(
+                                       $destTable, $srcTable, $varMap, $conds, $fname,
+                                       $insertOptions, $selectOptions, $selectJoinConds
+                               );
+                       }
                }
 
                return parent::nativeInsertSelect( $destTable, $srcTable, $varMap, $conds, $fname,
@@ -786,17 +763,17 @@ __INDEXATTR__;
        }
 
        public function wasDeadlock() {
-               // https://www.postgresql.org/docs/8.2/static/errcodes-appendix.html
+               // https://www.postgresql.org/docs/9.2/static/errcodes-appendix.html
                return $this->lastErrno() === '40P01';
        }
 
        public function wasLockTimeout() {
-               // https://www.postgresql.org/docs/8.2/static/errcodes-appendix.html
+               // https://www.postgresql.org/docs/9.2/static/errcodes-appendix.html
                return $this->lastErrno() === '55P03';
        }
 
        public function wasConnectionError( $errno ) {
-               // https://www.postgresql.org/docs/8.2/static/errcodes-appendix.html
+               // https://www.postgresql.org/docs/9.2/static/errcodes-appendix.html
                static $codes = [ '08000', '08003', '08006', '08001', '08004', '57P01', '57P03', '53300' ];
 
                return in_array( $errno, $codes, true );
@@ -809,17 +786,81 @@ __INDEXATTR__;
        public function duplicateTableStructure(
                $oldName, $newName, $temporary = false, $fname = __METHOD__
        ) {
-               $newName = $this->addIdentifierQuotes( $newName );
-               $oldName = $this->addIdentifierQuotes( $oldName );
+               $newNameE = $this->addIdentifierQuotes( $newName );
+               $oldNameE = $this->addIdentifierQuotes( $oldName );
+
+               $ret = $this->query( 'CREATE ' . ( $temporary ? 'TEMPORARY ' : '' ) . " TABLE $newNameE " .
+                       "(LIKE $oldNameE INCLUDING DEFAULTS INCLUDING INDEXES)", $fname );
+               if ( !$ret ) {
+                       return $ret;
+               }
+
+               $res = $this->query( 'SELECT attname FROM pg_class c'
+                       . ' JOIN pg_namespace n ON (n.oid = c.relnamespace)'
+                       . ' JOIN pg_attribute a ON (a.attrelid = c.oid)'
+                       . ' JOIN pg_attrdef d ON (c.oid=d.adrelid and a.attnum=d.adnum)'
+                       . ' WHERE relkind = \'r\''
+                       . ' AND nspname = ' . $this->addQuotes( $this->getCoreSchema() )
+                       . ' AND relname = ' . $this->addQuotes( $oldName )
+                       . ' AND adsrc LIKE \'nextval(%\'',
+                       $fname
+               );
+               $row = $this->fetchObject( $res );
+               if ( $row ) {
+                       $field = $row->attname;
+                       $newSeq = "{$newName}_{$field}_seq";
+                       $fieldE = $this->addIdentifierQuotes( $field );
+                       $newSeqE = $this->addIdentifierQuotes( $newSeq );
+                       $newSeqQ = $this->addQuotes( $newSeq );
+                       $this->query( 'CREATE ' . ( $temporary ? 'TEMPORARY ' : '' ) . " SEQUENCE $newSeqE", $fname );
+                       $this->query(
+                               "ALTER TABLE $newNameE ALTER COLUMN $fieldE SET DEFAULT nextval({$newSeqQ}::regclass)",
+                               $fname
+                       );
+               }
 
-               return $this->query( 'CREATE ' . ( $temporary ? 'TEMPORARY ' : '' ) . " TABLE $newName " .
-                       "(LIKE $oldName INCLUDING DEFAULTS INCLUDING INDEXES)", $fname );
+               return $ret;
+       }
+
+       public function resetSequenceForTable( $table, $fname = __METHOD__ ) {
+               $table = $this->tableName( $table, 'raw' );
+               foreach ( $this->getCoreSchemas() as $schema ) {
+                       $res = $this->query(
+                               'SELECT c.oid FROM pg_class c JOIN pg_namespace n ON (n.oid = c.relnamespace)'
+                               . ' WHERE relkind = \'r\''
+                               . ' AND nspname = ' . $this->addQuotes( $schema )
+                               . ' AND relname = ' . $this->addQuotes( $table ),
+                               $fname
+                       );
+                       if ( !$res || !$this->numRows( $res ) ) {
+                               continue;
+                       }
+
+                       $oid = $this->fetchObject( $res )->oid;
+                       $res = $this->query( 'SELECT adsrc FROM pg_attribute a'
+                               . ' JOIN pg_attrdef d ON (a.attrelid=d.adrelid and a.attnum=d.adnum)'
+                               . " WHERE a.attrelid = $oid"
+                               . ' AND adsrc LIKE \'nextval(%\'',
+                               $fname
+                       );
+                       $row = $this->fetchObject( $res );
+                       if ( $row ) {
+                               $this->query(
+                                       'SELECT ' . preg_replace( '/^nextval\((.+)\)$/', 'setval($1,1,false)', $row->adsrc ),
+                                       $fname
+                               );
+                               return true;
+                       }
+                       return false;
+               }
+
+               return false;
        }
 
        public function listTables( $prefix = null, $fname = __METHOD__ ) {
-               $eschema = $this->addQuotes( $this->getCoreSchema() );
+               $eschemas = implode( ',', array_map( [ $this, 'addQuotes' ], $this->getCoreSchemas() ) );
                $result = $this->query(
-                       "SELECT tablename FROM pg_tables WHERE schemaname = $eschema", $fname );
+                       "SELECT DISTINCT tablename FROM pg_tables WHERE schemaname IN ($eschemas)", $fname );
                $endArray = [];
 
                foreach ( $result as $table ) {
@@ -1010,6 +1051,29 @@ __INDEXATTR__;
                return $this->coreSchema;
        }
 
+       /**
+        * Return schema names for temporary tables and core application tables
+        *
+        * @since 1.31
+        * @return string[] schema names
+        */
+       public function getCoreSchemas() {
+               if ( $this->tempSchema ) {
+                       return [ $this->tempSchema, $this->getCoreSchema() ];
+               }
+
+               $res = $this->query(
+                       "SELECT nspname FROM pg_catalog.pg_namespace n WHERE n.oid = pg_my_temp_schema()", __METHOD__
+               );
+               $row = $this->fetchObject( $res );
+               if ( $row ) {
+                       $this->tempSchema = $row->nspname;
+                       return [ $this->tempSchema, $this->getCoreSchema() ];
+               }
+
+               return [ $this->getCoreSchema() ];
+       }
+
        public function getServerVersion() {
                if ( !isset( $this->numericVersion ) ) {
                        $conn = $this->getBindingHandle();
@@ -1042,18 +1106,24 @@ __INDEXATTR__;
                        $types = [ $types ];
                }
                if ( $schema === false ) {
-                       $schema = $this->getCoreSchema();
+                       $schemas = $this->getCoreSchemas();
+               } else {
+                       $schemas = [ $schema ];
                }
                $table = $this->realTableName( $table, 'raw' );
                $etable = $this->addQuotes( $table );
-               $eschema = $this->addQuotes( $schema );
-               $sql = "SELECT 1 FROM pg_catalog.pg_class c, pg_catalog.pg_namespace n "
-                       . "WHERE c.relnamespace = n.oid AND c.relname = $etable AND n.nspname = $eschema "
-                       . "AND c.relkind IN ('" . implode( "','", $types ) . "')";
-               $res = $this->query( $sql );
-               $count = $res ? $res->numRows() : 0;
+               foreach ( $schemas as $schema ) {
+                       $eschema = $this->addQuotes( $schema );
+                       $sql = "SELECT 1 FROM pg_catalog.pg_class c, pg_catalog.pg_namespace n "
+                               . "WHERE c.relnamespace = n.oid AND c.relname = $etable AND n.nspname = $eschema "
+                               . "AND c.relkind IN ('" . implode( "','", $types ) . "')";
+                       $res = $this->query( $sql );
+                       if ( $res && $res->numRows() ) {
+                               return true;
+                       }
+               }
 
-               return (bool)$count;
+               return false;
        }
 
        /**
@@ -1078,20 +1148,21 @@ __INDEXATTR__;
                        AND tgrelid=pg_class.oid
                        AND nspname=%s AND relname=%s AND tgname=%s
 SQL;
-               $res = $this->query(
-                       sprintf(
-                               $q,
-                               $this->addQuotes( $this->getCoreSchema() ),
-                               $this->addQuotes( $table ),
-                               $this->addQuotes( $trigger )
-                       )
-               );
-               if ( !$res ) {
-                       return null;
+               foreach ( $this->getCoreSchemas() as $schema ) {
+                       $res = $this->query(
+                               sprintf(
+                                       $q,
+                                       $this->addQuotes( $schema ),
+                                       $this->addQuotes( $table ),
+                                       $this->addQuotes( $trigger )
+                               )
+                       );
+                       if ( $res && $res->numRows() ) {
+                               return true;
+                       }
                }
-               $rows = $res->numRows();
 
-               return $rows;
+               return false;
        }
 
        public function ruleExists( $table, $rule ) {
@@ -1099,7 +1170,7 @@ SQL;
                        [
                                'rulename' => $rule,
                                'tablename' => $table,
-                               'schemaname' => $this->getCoreSchema()
+                               'schemaname' => $this->getCoreSchemas()
                        ]
                );
 
@@ -1107,19 +1178,19 @@ SQL;
        }
 
        public function constraintExists( $table, $constraint ) {
-               $sql = sprintf( "SELECT 1 FROM information_schema.table_constraints " .
-                       "WHERE constraint_schema = %s AND table_name = %s AND constraint_name = %s",
-                       $this->addQuotes( $this->getCoreSchema() ),
-                       $this->addQuotes( $table ),
-                       $this->addQuotes( $constraint )
-               );
-               $res = $this->query( $sql );
-               if ( !$res ) {
-                       return null;
+               foreach ( $this->getCoreSchemas() as $schema ) {
+                       $sql = sprintf( "SELECT 1 FROM information_schema.table_constraints " .
+                               "WHERE constraint_schema = %s AND table_name = %s AND constraint_name = %s",
+                               $this->addQuotes( $schema ),
+                               $this->addQuotes( $table ),
+                               $this->addQuotes( $constraint )
+                       );
+                       $res = $this->query( $sql );
+                       if ( $res && $res->numRows() ) {
+                               return true;
+                       }
                }
-               $rows = $res->numRows();
-
-               return $rows;
+               return false;
        }
 
        /**
@@ -1213,28 +1284,6 @@ SQL;
                return "'" . pg_escape_string( $conn, (string)$s ) . "'";
        }
 
-       /**
-        * Postgres specific version of replaceVars.
-        * Calls the parent version in Database.php
-        *
-        * @param string $ins SQL string, read from a stream (usually tables.sql)
-        * @return string SQL string
-        */
-       protected function replaceVars( $ins ) {
-               $ins = parent::replaceVars( $ins );
-
-               if ( $this->numericVersion >= 8.3 ) {
-                       // Thanks for not providing backwards-compatibility, 8.3
-                       $ins = preg_replace( "/to_tsvector\s*\(\s*'default'\s*,/", 'to_tsvector(', $ins );
-               }
-
-               if ( $this->numericVersion <= 8.1 ) { // Our minimum version
-                       $ins = str_replace( 'USING gin', 'USING gist', $ins );
-               }
-
-               return $ins;
-       }
-
        public function makeSelectOptions( $options ) {
                $preLimitTail = $postLimitTail = '';
                $startOpts = $useIndex = $ignoreIndex = '';
@@ -1332,7 +1381,7 @@ SQL;
                if ( !parent::lockIsFree( $lockName, $method ) ) {
                        return false; // already held
                }
-               // http://www.postgresql.org/docs/8.2/static/functions-admin.html#FUNCTIONS-ADVISORY-LOCKS
+               // http://www.postgresql.org/docs/9.2/static/functions-admin.html#FUNCTIONS-ADVISORY-LOCKS
                $key = $this->addQuotes( $this->bigintFromLockName( $lockName ) );
                $result = $this->query( "SELECT (CASE(pg_try_advisory_lock($key))
                        WHEN 'f' THEN 'f' ELSE pg_advisory_unlock($key) END) AS lockstatus", $method );
@@ -1342,7 +1391,7 @@ SQL;
        }
 
        public function lock( $lockName, $method, $timeout = 5 ) {
-               // http://www.postgresql.org/docs/8.2/static/functions-admin.html#FUNCTIONS-ADVISORY-LOCKS
+               // http://www.postgresql.org/docs/9.2/static/functions-admin.html#FUNCTIONS-ADVISORY-LOCKS
                $key = $this->addQuotes( $this->bigintFromLockName( $lockName ) );
                $loop = new WaitConditionLoop(
                        function () use ( $lockName, $key, $timeout, $method ) {
@@ -1362,7 +1411,7 @@ SQL;
        }
 
        public function unlock( $lockName, $method ) {
-               // http://www.postgresql.org/docs/8.2/static/functions-admin.html#FUNCTIONS-ADVISORY-LOCKS
+               // http://www.postgresql.org/docs/9.2/static/functions-admin.html#FUNCTIONS-ADVISORY-LOCKS
                $key = $this->addQuotes( $this->bigintFromLockName( $lockName ) );
                $result = $this->query( "SELECT pg_advisory_unlock($key) as lockstatus", $method );
                $row = $this->fetchObject( $result );
index cf5060e..edbcdfe 100644 (file)
@@ -27,6 +27,7 @@ use Psr\Log\LoggerInterface;
  * Manage savepoints within a transaction
  * @ingroup Database
  * @since 1.19
+ * @deprecated since 1.31, use IDatabase::startAtomic() and such instead.
  */
 class SavepointPostgres {
        /** @var DatabasePostgres Establish a savepoint within a transaction */
index 600f34a..53c3d33 100644 (file)
@@ -38,30 +38,34 @@ AND attname=%s;
 SQL;
 
                $table = $db->remappedTableName( $table );
-               $res = $db->query(
-                       sprintf( $q,
-                               $db->addQuotes( $db->getCoreSchema() ),
-                               $db->addQuotes( $table ),
-                               $db->addQuotes( $field )
-                       )
-               );
-               $row = $db->fetchObject( $res );
-               if ( !$row ) {
-                       return null;
+               foreach ( $db->getCoreSchemas() as $schema ) {
+                       $res = $db->query(
+                               sprintf( $q,
+                                       $db->addQuotes( $schema ),
+                                       $db->addQuotes( $table ),
+                                       $db->addQuotes( $field )
+                               )
+                       );
+                       $row = $db->fetchObject( $res );
+                       if ( !$row ) {
+                               continue;
+                       }
+                       $n = new PostgresField;
+                       $n->type = $row->typname;
+                       $n->nullable = ( $row->attnotnull == 'f' );
+                       $n->name = $field;
+                       $n->tablename = $table;
+                       $n->max_length = $row->attlen;
+                       $n->deferrable = ( $row->deferrable == 't' );
+                       $n->deferred = ( $row->deferred == 't' );
+                       $n->conname = $row->conname;
+                       $n->has_default = ( $row->atthasdef === 't' );
+                       $n->default = $row->adsrc;
+
+                       return $n;
                }
-               $n = new PostgresField;
-               $n->type = $row->typname;
-               $n->nullable = ( $row->attnotnull == 'f' );
-               $n->name = $field;
-               $n->tablename = $table;
-               $n->max_length = $row->attlen;
-               $n->deferrable = ( $row->deferrable == 't' );
-               $n->deferred = ( $row->deferred == 't' );
-               $n->conname = $row->conname;
-               $n->has_default = ( $row->atthasdef === 't' );
-               $n->default = $row->adsrc;
 
-               return $n;
+               return null;
        }
 
        function name() {
index 32d9008..1e8838e 100644 (file)
@@ -55,6 +55,7 @@ interface ILBFactory {
         *  - queryLogger: PSR-3 logger instance. [optional]
         *  - perfLogger: PSR-3 logger instance. [optional]
         *  - errorLogger: Callback that takes an Exception and logs it. [optional]
+        *  - deprecationLogger: Callback to log a deprecation warning. [optional]
         * @throws InvalidArgumentException
         */
        public function __construct( array $conf );
index bc428ec..b1ea810 100644 (file)
@@ -52,6 +52,8 @@ abstract class LBFactory implements ILBFactory {
        protected $perfLogger;
        /** @var callable Error logger */
        protected $errorLogger;
+       /** @var callable Deprecation logger */
+       protected $deprecationLogger;
        /** @var BagOStuff */
        protected $srvCache;
        /** @var BagOStuff */
@@ -109,7 +111,12 @@ abstract class LBFactory implements ILBFactory {
                $this->errorLogger = isset( $conf['errorLogger'] )
                        ? $conf['errorLogger']
                        : function ( Exception $e ) {
-                               trigger_error( E_USER_WARNING, get_class( $e ) . ': ' . $e->getMessage() );
+                               trigger_error( get_class( $e ) . ': ' . $e->getMessage(), E_USER_WARNING );
+                       };
+               $this->deprecationLogger = isset( $conf['deprecationLogger'] )
+                       ? $conf['deprecationLogger']
+                       : function ( $msg ) {
+                               trigger_error( $msg, E_USER_DEPRECATED );
                        };
 
                $this->profiler = isset( $conf['profiler'] ) ? $conf['profiler'] : null;
@@ -514,6 +521,7 @@ abstract class LBFactory implements ILBFactory {
                        'connLogger' => $this->connLogger,
                        'replLogger' => $this->replLogger,
                        'errorLogger' => $this->errorLogger,
+                       'deprecationLogger' => $this->deprecationLogger,
                        'hostname' => $this->hostname,
                        'cliMode' => $this->cliMode,
                        'agent' => $this->agent,
index 767cc49..715f4e4 100644 (file)
@@ -109,6 +109,7 @@ interface ILoadBalancer {
         *  - queryLogger: PSR-3 logger instance. [optional]
         *  - perfLogger: PSR-3 logger instance. [optional]
         *  - errorLogger : Callback that takes an Exception and logs it. [optional]
+        *  - deprecationLogger: Callback to log a deprecation warning. [optional]
         * @throws InvalidArgumentException
         */
        public function __construct( array $params );
index 7c1b9d9..db2ab1f 100644 (file)
@@ -111,6 +111,8 @@ class LoadBalancer implements ILoadBalancer {
 
        /** @var callable Exception logger */
        private $errorLogger;
+       /** @var callable Deprecation logger */
+       private $deprecationLogger;
 
        /** @var bool */
        private $disabled = false;
@@ -223,6 +225,11 @@ class LoadBalancer implements ILoadBalancer {
                        : function ( Exception $e ) {
                                trigger_error( get_class( $e ) . ': ' . $e->getMessage(), E_USER_WARNING );
                        };
+               $this->deprecationLogger = isset( $params['deprecationLogger'] )
+                       ? $params['deprecationLogger']
+                       : function ( $msg ) {
+                               trigger_error( $msg, E_USER_DEPRECATED );
+                       };
 
                foreach ( [ 'replLogger', 'connLogger', 'queryLogger', 'perfLogger' ] as $key ) {
                        $this->$key = isset( $params[$key] ) ? $params[$key] : new NullLogger();
@@ -1067,6 +1074,7 @@ class LoadBalancer implements ILoadBalancer {
                $server['connLogger'] = $this->connLogger;
                $server['queryLogger'] = $this->queryLogger;
                $server['errorLogger'] = $this->errorLogger;
+               $server['deprecationLogger'] = $this->deprecationLogger;
                $server['profiler'] = $this->profiler;
                $server['trxProfiler'] = $this->trxProfiler;
                // Use the same agent and PHP mode for all DB handles
index 1fe10d4..03bddfe 100644 (file)
        "cascadeprotected": "تمت حماية هذه الصفحة من التعديل لأنها مدمجة في {{PLURAL:$1||الصفحة التالية، والتي|الصفحتين التاليتين، واللتين|الصفحات التالية، والتي}} تم استعمال خاصية \"حماية الصفحات المدمجة\" {{PLURAL:$1||بها|بهما|بها}}:\n$2",
        "namespaceprotected": "لا تمتلك الصلاحية لتعديل الصفحات في نطاق '''$1'''.",
        "customcssprotected": "أنت لا تمتلك السماح لتعديل صفحة الCSS هذه، لأنها تحتوي على الإعدادات الشخصية لمستخدم آخر.",
+       "customjsonprotected": "ليست لديك صلاحية تحرير صفحة جسون هذه لأنها تحتوي على إعدادات شخصية لمستخدم آخر.",
        "customjsprotected": "أنت لا تمتلك السماح لتعديل صفحة الجافاسكريبت هذه، لأنها تحتوي على الإعدادات الشخصية لمستخدم آخر.",
        "mycustomcssprotected": "ليس لديك صلاحية تعديل هذه الصفحة للطرز المتراصة.",
+       "mycustomjsonprotected": "ليست لديك صلاحية تحرير صفحة جسون هذه",
        "mycustomjsprotected": "ليس لديك صلاحية تعديل صفحة جافاسكربت هذه.",
        "myprivateinfoprotected": "ليس لديك صلاحية تعديل معلوماتك الخاصة.",
        "mypreferencesprotected": "ليس لديك صلاحية تعديل تفضيلاتك.",
        "savechanges": "احفظ التغييرات",
        "publishpage": "نشر الصفحة",
        "publishchanges": "نشر التغييرات",
+       "savearticle-start": "احفظ الصفحة…",
+       "savechanges-start": "حفظ التغييرات...",
+       "publishpage-start": "نشر الصفحة...",
+       "publishchanges-start": "نشر التغييرات...",
        "preview": "عرض مسبق",
        "showpreview": "أظهر معاينة",
        "showdiff": "عرض التغييرات",
        "blocked-notice-logextract": "هذا المستخدم ممنوع حاليا.\nآخر مدخلة في سجل المنع موفرة بالأسفل كمرجع:",
        "clearyourcache": "<strong>ملاحظة:</strong> بعد الحفظ، أنت قد تحتاج إلى إفراغ الكاش الخاص بمتصفحك لرؤية التغييرات.\n* <strong>فايرفوكس / سافاري:</strong> أمسك <em>Shift</em> أثناء ضغط <em>Reload</em>، أو اضغط على إما <em>Ctrl-F5</em> أو <em>Ctrl-R</em> (<em>⌘-R</em> على ماك)\n* <strong>جوجل كروم:</strong> اضغط <em>Ctrl-Shift-R</em> (<em>⌘-Shift-R</em> على ماك)\n* <strong>إنترنت إكسبلورر:</strong> أمسك <em>Ctrl</em> أثناء ضغط <em>Refresh</em>، أو اضغط <em>Ctrl-F5</em>\n* <strong>أوبرا:</strong> اذهب إلى <em>Menu → Settings</em> (<em>Opera → Preferences</em> على ماك) ثم إلى <em>Privacy & security → Clear browsing data → Cached images and files</em>.",
        "usercssyoucanpreview": "'''ملاحظة:''' استعمل زر \"{{int:showpreview}}\" لتجربة CSS الجديد قبل حفظ الصفحة.",
+       "userjsonyoucanpreview": "<strong>نصيحة:</strong>  استخدم الزر \"{{int:showpreview}}\" لاختبار جسون الجديد قبل الحفظ.",
        "userjsyoucanpreview": "'''ملاحظة:''' استعمل زر \"{{int:showpreview}}\" لتجربة جافاسكربت الجديدة قبل حفظ الصفحة.",
        "usercsspreview": "'''تذكر أنك تقوم بعرض الأنماط المتراصة (CSS) الخاصة بك فقط\nلم يتم حفظها بعد!'''",
+       "userjsonpreview": "<strong>تذكر أنك تختبر/تستعرض تهيئة جسون للمستخدم فقط،\nلم يتم حفظها بعد!</strong>",
        "userjspreview": "'''تذكر أنك فقط تجرب/تعاين جافاسكربت.'''\n'''لم يتم الحفظ بعد!'''",
        "sitecsspreview": "''' تذكر أنك فقط في وضع المعاينة لهذا CSS ''' \n''' ولم يتم حفظ الصفحة بعد! '''",
+       "sitejsonpreview": "<strong>تذكر أنك تقوم بمعاينة تهيئة جسون هذه فقط،\nلم يتم حفظها بعد!</strong>",
        "sitejspreview": "''' تذكر أنك فقط في وضع المعاينة لكود JavaScript هذا''' \n''' ولم يتم حفظه بعد! '''",
-       "userinvalidconfigtitle": "'''تحذير:''' لا توجد واجهة  \"$1\".\nتذكر أن ملفات ال.css و ال.js تستخدم حروف صغيرة في العنوان ، كمثال {{ns:user}}:Foo/vector.css و ليس {{ns:user}}:Foo/Vector.css.",
+       "userinvalidconfigtitle": "<strong>تحذير:</strong> T لا توجد واجهة \"$1\".\nصفحات Custom .css و.json و.js تستخدم حروفا صغيرة في العنوان، مثل {{ns:user}}:Foo/vector.css على عكس {{ns:user}}:Foo/Vector.css.",
        "updated": "(محدثة)",
        "note": "'''ملاحظة:'''",
        "previewnote": "'''تذكر أن هذه مجرد معاينة أولية.'''\nلم تحفظ تغييراتك إلى الآن!",
        "default": "افتراضي",
        "prefs-files": "ملفات",
        "prefs-custom-css": "CSS مخصص",
+       "prefs-custom-json": "جسون مخصص",
        "prefs-custom-js": "جافاسكربت مخصص",
-       "prefs-common-config": "CSS وجافاسكربت مشترك لجميع الواجهات:",
+       "prefs-common-config": "جافاسكربت/CSS/JSON مشترك لجميع الواجهات:",
        "prefs-reset-intro": "يمكنك استخدام هذه الصفحة لإعادة تفضيلاتك للحالة الافتراضية للموقع.\nلن تستطيع استرجاع الحالة السابقة.",
        "prefs-emailconfirm-label": "تأكيد البريد الإلكتروني:",
        "youremail": "البريد:",
        "right-editcontentmodel": "عدل طريقة محتوى صفحة",
        "right-editinterface": "تعديل واجهة المستخدم",
        "right-editusercss": "تعديل ملفات CSS للمستخدمين الآخرين",
+       "right-edituserjson": "تعديل ملفات جسون للمستخدمين الآخرين",
        "right-edituserjs": "تعديل ملفات جافاسكريبت للمستخدمين الآخرين",
        "right-editmyusercss": "تعديل ملفات CSS للمستخدم نفسه",
+       "right-editmyuserjson": "تعديل ملفات جسون للمستخدم نفسه",
        "right-editmyuserjs": "تعديل ملفات جافاسكربت للمستخدم نفسه",
        "right-viewmywatchlist": "عرض قائمة مراقبتك",
        "right-editmywatchlist": "حرر قائمة مراقبتك. لاحظ أن بعض الإجراءات لا تزال تضيف الصفحات حتى بدون هذا الحق.",
        "grant-createaccount": "إنشاء حسابات",
        "grant-createeditmovepage": "إنشاء وتعديل ونقل الصفحات",
        "grant-delete": "حذف الصفحات والمراجعات ومدخلات السجلات",
-       "grant-editinterface": "تعديل نطاق ميدياويكي والCSS/JavaScript الخاصة بالمستخدم",
-       "grant-editmycssjs": "تعديل الCSS/JavaScript الخاصة بحسابك",
+       "grant-editinterface": "تعديل نطاق ميدياويكي وCSS/جافا سكريت المستخدم",
+       "grant-editmycssjs": "تعديل CSS/جافا سكريت/جسون الخاصة بحسابك",
        "grant-editmyoptions": "تعديل تفضيلاتك",
        "grant-editmywatchlist": "تعديل قائمة مراقبتك",
        "grant-editpage": "تعديل صفحات موجودة",
        "group-bot.css": "/* الأنماط المتراصة CSS المعروضة هنا ستؤثر على البوتات فقط */",
        "group-sysop.css": "/* الأنماط المتراصة CSS المعروضة هنا ستؤثر على الإداريين فقط */",
        "group-bureaucrat.css": "/* الأنماط المتراصة CSS المعروضة هنا ستؤثر على البيروقراطيين فقط */",
+       "common.json": "/* سيتم تحميل أي جسون هنا لجميع المستخدمين في كل تحميل للصفحة. */",
        "common.js": "/* الجافاسكريبت الموضوع هنا سيتم تحميله لكل المستخدمين مع كل تحميل للصفحة. */",
        "group-autoconfirmed.js": "/* أي جافاسكريبت هنا سيتم تحميلها للمستخدمين المؤكدين تلقائيا فقط */",
        "group-user.js": "/* أي JavaScript هنا سيتم تحميله للمستخدمين المسجلين فقط */",
        "unlinkaccounts-success": "الحساب تم فك وصله.",
        "authenticationdatachange-ignored": "تغيير بيانات التحقق لم يتم التعامل معه. ربما لم يتم ضبط موفر؟",
        "userjsispublic": "من فضلك لاحظ: صفحات الجافاسكريبت الفرعية لا ينبغي أن تحتوي غلى بيانات سرية بما أنها يمكن رؤيتها بواسطة المستخدمين الآخرين.",
+       "userjsonispublic": "الرجاء ملاحظة أنه: يجب ألا تحتوي الصفحات الفرعية لجسون على بيانات سرية لأنها قابلة للعرض من قبل المستخدمين الآخرين.",
        "usercssispublic": "من فضل لاحظ: صفحات الCSS الفرعية لا ينبغي أن تحتوي على بيانات سرية بما أنها يمكن رؤيتها بواسطة المستخدمين الآخرين.",
        "restrictionsfield-badip": "عنوان أيبي أو نطاق غير صحيح: $1",
        "restrictionsfield-label": "نطاقات الأيبي المسموح بها:",
index 8c4be62..3daacc7 100644 (file)
        "publishchanges": "Апублікаваць зьмены",
        "savearticle-start": "Захаваць старонку…",
        "savechanges-start": "Захаваць зьмены…",
+       "publishpage-start": "Апублікаваць старонку…",
        "preview": "Папярэдні прагляд",
        "showpreview": "Праглядзець",
        "showdiff": "Паказаць зьмены",
        "grant-editinterface": "Рэдагаваньне прасторы назваў MediaWiki і CSS/JSON/JavaScript удзельніка",
        "grant-editmycssjs": "Рэдагаваньне вашага CSS/JSON/JavaScript",
        "grant-editmyoptions": "Рэдагаваньне вашых наладаў удзельніка",
-       "grant-editmywatchlist": "Рэдагаваць ваш сьпіс назіраньня",
+       "grant-editmywatchlist": "Рэдагаваньне вашага сьпісу назіраньня",
        "grant-editpage": "Рэдагаваць існыя старонкі",
        "grant-editprotected": "Рэдагаваць абароненыя старонкі",
        "grant-highvolume": "Рэдагаваньне з высокай інтэнсіўнасьцю",
index 7b3628f..d4d2682 100644 (file)
        "wrongpasswordempty": "Не е въведена парола.\nОпитайте отново.",
        "passwordtooshort": "Необходимо е паролата да съдържа поне {{PLURAL:$1|1 знак|$1 знака}}.",
        "passwordtoolong": "Паролата не може да бъде по-дългa от {{PLURAL:$1|1 знак|$1 знака}}.",
-       "passwordtoopopular": "ЧеÑ\81Ñ\82о Ð¸Ð·Ð¿Ð¾Ð»Ð·Ð²Ð°Ð½Ð¸ Ð¿Ð°Ñ\80оли Ð½Ðµ Ð¼Ð¾Ð³Ð°Ñ\82 Ð´Ð° Ð±Ñ\8aдаÑ\82 Ð¿Ð¾Ð»Ð·Ð²Ð°Ð½Ð¸. Ð\9cолÑ\8f, Ð¸Ð·Ð±ÐµÑ\80еÑ\82е Ð¿Ð¾-Ñ\83никална Ð¿Ð°Ñ\80ола.",
+       "passwordtoopopular": "ЧеÑ\81Ñ\82о Ð¸Ð·Ð¿Ð¾Ð»Ð·Ð²Ð°Ð½Ð¸ Ð¿Ð°Ñ\80оли Ð½Ðµ Ð¼Ð¾Ð³Ð°Ñ\82 Ð´Ð° Ð±Ñ\8aдаÑ\82 Ð¿Ð¾Ð»Ð·Ð²Ð°Ð½Ð¸. Ð\9cолÑ\8f, Ð¸Ð·Ð±ÐµÑ\80еÑ\82е Ð¿Ð°Ñ\80ола, ÐºÐ¾Ñ\8fÑ\82о Ðµ Ð¿Ð¾-Ñ\82Ñ\80Ñ\83дна Ð·Ð° Ð¿Ð¾Ð·Ð½Ð°Ð²Ð°Ð½Ðµ.",
        "password-name-match": "Паролата Ви трябва да се различава от потребителското Ви име.",
        "password-login-forbidden": "Използването на това потребителско име и парола е забранено.",
        "mailmypassword": "Възстановяване на парола",
        "userjspreview": "<strong>Не забравяйте, че това е само изпробване/предварителен преглед на кода на JavaScript.\nСтраницата все още не е съхранена!</strong>",
        "sitecsspreview": "<strong>Не забравяйте, че това е само предварителен преглед на този CSS.\nТой все още не е съхранен!</strong>",
        "sitejspreview": "<strong>Не забравяйте, че това е само предварителен преглед на този JavaScript код.\nТой все още не е съхранен!</strong>",
-       "userinvalidconfigtitle": "<strong>Ð\92нимание:</strong> Ð\9dе Ñ\81Ñ\8aÑ\89еÑ\81Ñ\82вÑ\83ва Ð¾Ð±Ð»Ð¸Ðº â\80\9e$1â\80\9c.\nÐ\9dеобÑ\85одимо Ðµ Ð´Ð° Ñ\81е Ð·Ð½Ð°Ðµ, Ñ\87е Ð¸Ð¼ÐµÐ½Ð°Ñ\82а Ð½Ð° Ð¿Ð¾Ñ\82Ñ\80ебиÑ\82елÑ\81киÑ\82е Ð²Ð¸ Ñ\81Ñ\82Ñ\80аниÑ\86и Ð·Ð° CSS Ð¸ JavaScript Ñ\82Ñ\80Ñ\8fбва Ð´Ð° Ñ\81е Ñ\81Ñ\8aÑ\81Ñ\82оÑ\8fÑ\82 Ð¾Ñ\82 Ð¼Ð°Ð»ÐºÐ¸ Ð±Ñ\83кви, Ð½Ð°Ð¿Ñ\80имеÑ\80: â\80\9e{{ns:user}}:Ð\98ван/vector.cssâ\80\9c (а Ð½Ðµ â\80\9e{{ns:user}}:Ð\98ван/Vector.cssâ\80\9c).",
+       "userinvalidconfigtitle": "<strong>Ð\92нимание:</strong> Ð\9dе Ñ\81Ñ\8aÑ\89еÑ\81Ñ\82вÑ\83ва Ð¾Ð±Ð»Ð¸Ðº â\80\9e$1â\80\9c.\nÐ\98менаÑ\82а Ð½Ð° Ð¿Ð¾Ñ\82Ñ\80ебиÑ\82елÑ\81ки .css, .json Ð¸ .js Ñ\81Ñ\82Ñ\80аниÑ\86и Ñ\82Ñ\80Ñ\8fбва Ð´Ð° Ð¸Ð·Ð¿Ð¾Ð»Ð·Ð²Ð°Ñ\82 Ð¼Ð°Ð»ÐºÐ¸ Ð±Ñ\83кви, Ð½Ð°Ð¿Ñ\80имеÑ\80: {{ns:user}}:Ð\98ван/vector.css (а Ð½Ðµ {{ns:user}}:Ð\98ван/Vector.css).",
        "updated": "(обновена)",
        "note": "<strong>Забележка:</strong>",
        "previewnote": "<strong>Обърнете внимание, че това е само предварителен преглед.</strong>\nПромените все още не са съхранени!",
        "expansion-depth-exceeded-category-desc": "Страницата превишава максимално допустимата дълбочина на разгръщане.",
        "expansion-depth-exceeded-warning": "Страницата е превишила разрешената дълбочина на разгръщане",
        "parser-unstrip-loop-warning": "Открито е ''unstrip'' зацикляне",
-       "unstrip-depth-warning": "''Unstrip'' лимита на рекурсия превишава ($1)",
+       "unstrip-depth-warning": "Превишено ограничение на дълбочина ($1)",
        "undo-success": "Редакцията може да бъде върната.\nПрегледайте долното сравнение и се уверете, че наистина искате да го направите. След това съхранете страницата, за да извършите връщането.",
        "undo-failure": "Редакцията не може да бъде върната поради конфликтни междинни редакции.",
        "undo-norev": "Редакцията не може да бъде върната, тъй като не съществува или е била изтрита.",
        "filerevert-intro": "Възвръщане на <strong>[[Media:$1|$1]]</strong> към [$4 версията от $3, $2].",
        "filerevert-comment": "Причина:",
        "filerevert-defaultcomment": "Възвръщане към версия от $2, $1 ($3)",
-       "filerevert-submit": "Ð\92Ñ\8aзвÑ\80Ñ\8aÑ\89ане",
+       "filerevert-submit": "Връщане",
        "filerevert-success": "Файлът <strong>[[Media:$1|$1]]</strong> беше възвърнат към [$4 версия от $3, $2].",
        "filerevert-badversion": "Не съществува предишна локална версия на файла със зададения времеви отпечатък.",
        "filedelete": "Изтриване на $1",
        "protect_expiry_invalid": "Невалиден срок на изтичане.",
        "protect_expiry_old": "Срокът на изтичане е минал.",
        "protect-unchain-permissions": "Позволяване на по-нататъшни възможности за защита",
-       "protect-text": "Тук можете да прегледате и промените нивото на защита на страницата '''$1'''.",
-       "protect-locked-blocked": "Не можете да променяте нивата на защита на страниците, докато сте блокиран(а). Текущите настройки за страницата „'''$1'''“ са:",
-       "protect-locked-dblock": "Нивата на защита на страниците не могат да бъдат променяни, защото базата от данни е заключена. Ето текущите настройки за страницата „'''$1'''“:",
+       "protect-text": "Тук можете да прегледате и промените нивото на защита на страницата <strong>$1</strong>.",
+       "protect-locked-blocked": "Не можете да променяте нивата на защита на страниците, докато сте блокиран(а).\nТекущите настройки за страницата <strong>$1</strong> са:",
+       "protect-locked-dblock": "Нивата на защита на страниците не могат да бъдат променяни, защото базата от данни е заключена.\nЕто текущите настройки за страницата <strong>$1</strong>:",
        "protect-locked-access": "Нямате правото да променяте нивата на защита на страниците. Ето текущите настройки за страницата „'''$1'''“:",
        "protect-cascadeon": "Тази страница е защитена против редактиране, защото е включена в {{PLURAL:$1|следната страница, която от своя страна има|следните страници, които от своя страна имат}} каскадна защита.\nМожете да промените нивото на защита на страницата, но това няма да повлияе върху каскадната защита.",
        "protect-default": "Позволяване за всички потребители",
index 6ce3887..6bbd3d8 100644 (file)
        "missing-revision": "АгӀона «{{FULLPAGENAME}}» верси $1 яц.\n\nИштта хуьйла ширъелла дӀаяьккхина агӀонан хьажораган дихьа делча.\nМа-дара хила мега [{{fullurl:{{#Special:Log}}/delete|page={{FULLPAGENAMEE}}}} дӀайаьхарш йолу тептар] чохь.",
        "userpage-userdoesnotexist": "ХӀокху декъашхочун «<nowiki>$1</nowiki>» хьесапан дӀаяздар дац. Тешна хила, хьуна баккъалла лаьий кхолла а, я хӀокху агӀонан хийцам бан а.",
        "userpage-userdoesnotexist-view": "«$1» иштта декъашхочун дӀаяздар дац.",
-       "blocked-notice-logextract": "Ð¥Ó\80аÑ\80а Ð´ÐµÐºÑ\8aаÑ\88Ñ\85оÑ\87Ñ\83н Ð´Ó\80аÑ\8fздаÑ\80 Ð±Ð»Ð¾ÐºÑ\82оÑ\8cÑ\85на Ð´Ñ\83.\nÐ\9bаÑ\85аÑ\85Ñ\8c Ð³Ð¾Ð¹Ñ\82Ñ\83 Ð±Ð»Ð¾ÐºÑ\82оÑ\85аÑ\80ийн Ñ\82епÑ\82аÑ\80 Ñ\87Ñ\83Ñ\80а Ñ\82Ó\80аÑ\8cÑ\85Ñ\85Ñ\8cаÑ\80леÑ\80а Ð´Ó\80аÑ\8fздаÑ\80:",
+       "blocked-notice-logextract": "ХӀара декъашхочун дӀаяздар блоктоьхна ду.\nЛахахь гойту блоктохарийн тептар чура тӀаьххьара дӀаяздар:",
        "clearyourcache": "'''Билгалдаккхар.''' Ӏалашйинчул тӀехьа хийцамаш га браузеран кэш цӀанъян езаш хила мега.\n* '''Firefox / Safari:''' ''Shift'' цӀе йолу пиллиг лаьцна битна, гӀирсийн панелан тӀера тӀетаӀе ''Карлаяккха'' я ''Ctrl-F5'' я ''Ctrl-R'' (''⌘-R'' Mac тӀехь)\n* '''Google Chrome:''' ТӀетаӀе ''Ctrl-Shift-R'' (''⌘-Shift-R'' Mac тӀехь)\n* '''Internet Explorer:''' ''Ctrl'' лаьцна йитан, тӀетаӀе ''Карлаяккха'' я тӀетаӀе ''Ctrl-F5''\n* '''Opera:''' Кэш цӀанъяр харжа меню ''Инструменты → Настройки'' чохь",
        "usercssyoucanpreview": "'''ДӀаалар.''' ТӀетаӀае кнопка «{{int:showpreview}}», хьажа хьай керла CSS-файл Ӏалаш яле.",
        "userjsyoucanpreview": "'''ДӀаалар.''' ТӀетаӀае кнопка «{{int:showpreview}}», хьажа хьай керла JS-файл Ӏалаш яле.",
        "sp-contributions-logs": "тéптарш",
        "sp-contributions-talk": "дийцаре",
        "sp-contributions-userrights": "декъашхочун бакъонашна урхалладар",
-       "sp-contributions-blocked-notice": "Ð¥Ó\80аÑ\80а Ð´ÐµÐºÑ\8aаÑ\88Ñ\85оÑ\87Ñ\83н Ð´Ó\80аÑ\8fздаÑ\80 Ð±Ð»Ð¾ÐºÑ\82оÑ\8cÑ\85на Ð´Ñ\83.\nÐ\9bаÑ\85аÑ\85Ñ\8c Ð³Ð¾Ð¹Ñ\82Ñ\83 Ð±Ð»Ð¾ÐºÑ\82оÑ\85аÑ\80ийн Ñ\82епÑ\82аÑ\80 Ñ\87Ñ\83Ñ\80а Ñ\82Ó\80аÑ\8cÑ\85Ñ\85Ñ\8cаÑ\80леÑ\80а Ð´Ó\80аÑ\8fздаÑ\80:",
-       "sp-contributions-blocked-notice-anon": "Ð¥Ó\80аÑ\80а IP-адÑ\80еÑ\81 Ñ\85Ó\80инÑ\86а Ð±Ð»Ð¾ÐºÑ\82оÑ\8cÑ\85на Ð´Ñ\83.\nÐ\9bаÑ\85аÑ\85Ñ\8c Ð³Ð¾Ð¹Ñ\82Ñ\83 Ð±Ð»Ð¾ÐºÑ\82оÑ\85аÑ\80ийн Ñ\82епÑ\82аÑ\80 Ñ\87Ñ\83Ñ\80а Ñ\82Ó\80аÑ\8cÑ\85Ñ\85Ñ\8cаÑ\80леÑ\80а Ð´Ó\80аÑ\8fздаÑ\80:",
+       "sp-contributions-blocked-notice": "ХӀара декъашхочун дӀаяздар блоктоьхна ду.\nЛахахь гойту блоктохарийн тептар чура тӀаьххьара дӀаяздар:",
+       "sp-contributions-blocked-notice-anon": "ХӀара IP-адрес хӀинца блоктоьхна ду.\nЛахахь гойту блоктохарийн тептар чура тӀаьххьара дӀаяздар:",
        "sp-contributions-search": "Къинхьегам лахар",
        "sp-contributions-username": "IP-адрес я декъашхочун цӀе:",
        "sp-contributions-toponly": "Гайта тӀаьххьарлера хийцамаш",
index fa7ba41..162b56d 100644 (file)
        "cascadeprotected": "Tato stránka je zamčena, neboť je vložena na {{PLURAL:$1|následující stránku, zamčenou|následující stránky, zamčené}} kaskádovým zámkem:\n$2",
        "namespaceprotected": "Nemáte povoleno editovat stránky ve jmenném prostoru <strong>$1</strong>.",
        "customcssprotected": "Nemáte povoleno editovat tuto stránku s CSS, protože obsahuje osobní nastavení jiného uživatele.",
+       "customjsonprotected": "Nemáte povoleno editovat tuto stránku s JSONem, protože obsahuje osobní nastavení jiného uživatele.",
        "customjsprotected": "Nemáte povoleno editovat tuto stránku s JavaScriptem, protože obsahuje osobní nastavení jiného uživatele.",
        "mycustomcssprotected": "Nemáte oprávnění editovat tuto stránku s CSS.",
+       "mycustomjsonprotected": "Nemáte oprávnění editovat tuto stránku s JSONem.",
        "mycustomjsprotected": "Nemáte oprávnění editovat tuto stránku s JavaScriptem.",
        "myprivateinfoprotected": "Nemáte oprávnění měnit své soukromé údaje.",
        "mypreferencesprotected": "Nemáte oprávnění změnit svá nastavení.",
        "blocked-notice-logextract": "{{GENDER:$1|Tento uživatel|Tato uživatelka}} je momentálně {{GENDER:$1|zablokován|zablokována}}.\nZde je pro přehled zobrazen nejnovější záznam z knihy zablokování:",
        "clearyourcache": "<strong>Poznámka:</strong> Po uložení musíte smazat cache vašeho prohlížeče, jinak změny neuvidíte.\n* <strong>Firefox / Safari:</strong> Při kliknutí na <em>Aktualizovat</em> držte <em>Shift</em> nebo stiskněte <em>Ctrl-F5</em> nebo <em>Ctrl-R</em> (na Macu <em>⌘-R</em>)\n* <strong>Google Chrome:</strong> Stiskněte <em>Ctrl-Shift-R</em> (na Macu <em>⌘-Shift-R</em>)\n* <strong>Internet Explorer:</strong> Při kliknutí na <em>Aktualizovat</em> držte <em>Ctrl</em> nebo stiskněte <em>Ctrl-F5</em>\n* <strong>Opera:</strong> Jděte do <em>Menu → Nastavení</em> (na Macu <em>Opera → Nastavení</em>) a tam pak <em>Soukromí & bezpečnost → Vymazat údaje o prohlížení → Obrázky a soubory z cache</em>",
        "usercssyoucanpreview": "<strong>Tip:</strong> Použijte tlačítko „{{int:showpreview}}“ k testování vašeho nového CSS před uložením.",
+       "userjsonyoucanpreview": "<strong>Tip:</strong> Použijte tlačítko „{{int:showpreview}}“ k testování vašeho nového JSONu před uložením.",
        "userjsyoucanpreview": "<strong>Tip:</strong> Použijte tlačítko „{{int:showpreview}}“ k testování vašeho nového JavaScriptu před uložením.",
        "usercsspreview": "<strong>Pamatujte, že si prohlížíte jen náhled vašeho uživatelského CSS, jelikož dosud nebyl uložen!</strong>",
+       "userjsonpreview": "<strong>Pamatujte, že testujete a prohlížíte pouze náhled svého uživatelského JSONu, jelikož dosud nebyl uložen!</strong>",
        "userjspreview": "<strong>Pamatujte, že testujete a prohlížíte pouze náhled svého uživatelského JavaScriptu, jelikož dosud nebyl uložen!</strong>",
        "sitecsspreview": "<strong>Pamatujte, že si prohlížíte jen náhled tohoto CSS, jelikož dosud nebylo uloženo!</strong>",
+       "sitejsonpreview": "<strong>Pamatujte, že testujete a prohlížíte pouze náhled tohoto JSONu, jelikož dosud nebyl uložen!</strong>",
        "sitejspreview": "<strong>Pamatujte, že testujete a prohlížíte pouze náhled tohoto JavaScriptu, jelikož dosud nebyl uložen!</strong>",
-       "userinvalidconfigtitle": "<strong>Varování:</strong> Vzhled „$1“ neexistuje. Nezapomeňte, že uživatelské .css a .js soubory používají malá písmena, např. {{ns:user}}:{{BASEPAGENAME}}/vector.css, nikoli {{ns:user}}:{{BASEPAGENAME}}/Vector.css.",
+       "userinvalidconfigtitle": "<strong>Varování:</strong> Vzhled „$1“ neexistuje. Nezapomeňte, že uživatelské .css, .json a .js soubory používají malá písmena, např. {{ns:user}}:{{BASEPAGENAME}}/vector.css, nikoli {{ns:user}}:{{BASEPAGENAME}}/Vector.css.",
        "updated": "(Změna uložena)",
        "note": "<strong>Poznámka:</strong>",
        "previewnote": "<strong>Pamatujte, že toto je pouze náhled.</strong>\nZměny dosud nebyly uloženy!",
        "default": "implicitní",
        "prefs-files": "Soubory",
        "prefs-custom-css": "Uživatelské CSS",
+       "prefs-custom-json": "Uživatelský JSON",
        "prefs-custom-js": "Uživatelský JavaScript",
-       "prefs-common-config": "Sdílené CSS/JavaScript pro všechny styly:",
+       "prefs-common-config": "Sdílené CSS/JSON/JavaScript pro všechny styly:",
        "prefs-reset-intro": "Pomocí této stránky můžete všechna nastavení vrátit na implicitní hodnoty.\nTuto operaci nelze vrátit zpět.",
        "prefs-emailconfirm-label": "Ověření e-mailu:",
        "youremail": "E-mail:",
        "right-editcontentmodel": "Editace modelu obsahu stránky",
        "right-editinterface": "Editace zpráv uživatelského rozhraní",
        "right-editusercss": "Editace CSS souborů jiných uživatelů",
+       "right-edituserjson": "Editace souborů s JSONem jiných uživatelů",
        "right-edituserjs": "Editace JavaScriptových souborů jiných uživatelů",
        "right-editmyusercss": "Editace vlastních uživatelských CSS souborů",
+       "right-editmyuserjson": "Editace vlastní uživatelských souborů s JSONem",
        "right-editmyuserjs": "Editace vlastních uživatelských JavaScriptových souborů",
        "right-viewmywatchlist": "Prohlížení vlastního seznamu sledovaných stránek",
        "right-editmywatchlist": "Editace vlastního seznamu sledovaných stránek. Uvědomte si, že některé akce do něj mohou přidat stránky i bez tohoto oprávnění.",
        "grant-createaccount": "Zakládat účty",
        "grant-createeditmovepage": "Vytvářet, editovat a přesouvat stránky",
        "grant-delete": "Mazat stránky, revize a protokolovací záznamy",
-       "grant-editinterface": "Editovat jmenný prostor MediaWiki a uživatelské CSS/JavaScript",
-       "grant-editmycssjs": "Editovat váš uživatelský CSS/JavaScript",
+       "grant-editinterface": "Editovat jmenný prostor MediaWiki a uživatelské CSS/JSON/JavaScript",
+       "grant-editmycssjs": "Editovat váš uživatelský CSS/JSON/JavaScript",
        "grant-editmyoptions": "Změna vašich uživatelských nastavení",
        "grant-editmywatchlist": "Upravovat váš seznam sledovaných stránek",
        "grant-editpage": "Editovat existující stránky",
        "unlinkaccounts-success": "Propojení účtu bylo zrušeno.",
        "authenticationdatachange-ignored": "Změna autentizačních údajů nebyla zpracována. Možná není nakonfigurován žádný poskytovatel?",
        "userjsispublic": "Uvědomte si prosím, že podstránky s JavaScriptem by neměly obsahovat tajné údaje, protože jsou viditelné ostatním uživatelům.",
+       "userjsonispublic": "Uvědomte si prosím, že podstránky s JSONem by neměly obsahovat tajné údaje, protože jsou viditelné ostatním uživatelům.",
        "usercssispublic": "Uvědomte si prosím, že podstránky s CSS by neměly obsahovat tajné údaje, protože jsou viditelné ostatním uživatelům.",
        "restrictionsfield-badip": "Neplatná IP adresa nebo rozsah: $1",
        "restrictionsfield-label": "Povolené rozsahy IP adres:",
index 6be7e50..5f011fa 100644 (file)
        "apisandbox-dynamic-error-exists": "Ein Parameter mit dem Namen „$1“ ist bereits vorhanden.",
        "apisandbox-deprecated-parameters": "Veraltete Parameter",
        "apisandbox-fetch-token": "Den Token automatisch ausfüllen",
+       "apisandbox-add-multi": "Hinzufügen",
        "apisandbox-submit-invalid-fields-title": "Einige Felder sind ungültig",
        "apisandbox-submit-invalid-fields-message": "Korrigiere bitte die markierten Felder und versuche es erneut.",
        "apisandbox-results": "Ergebnisse",
index 930822a..06081a4 100644 (file)
        "tag-mw-removed-redirect": "Widjerfeerang wechnimen",
        "tag-mw-changed-redirect-target": "Widjerfeerang feranert",
        "tag-mw-rollback": "Turagsaat",
+       "tag-mw-undo": "Turag saaten",
        "tags-title": "Kääntiaken",
        "tags-intro": "Det sidj wiset kääntiaken, diar för't bewerkin brükt wurd, an wat jo men.",
        "tags-tag": "Kääntiaken-nööm",
index d61f188..b5363c8 100644 (file)
        "viewsource": "Wè tèks sours",
        "viewsource-title": "Wè sours-a di $1",
        "actionthrottled": "Aksyon limité",
+       "actionthrottledtext": "Pou briga kont abi-ya, itilizasyon-an di sa aksyon sa limité à roun sèrten nonm di fwè annan roun laps di tan asé kourt é zòt dépasé sa limit.\nSouplé, éséyé òkò annan tchèk minout.",
+       "protectedpagetext": "Sa paj té protéjé pou anpéché so modifikasyon oben dé ròt aksyon.",
        "viewsourcetext": "Zòt pé wè é kopyé kontni di sa paj.",
+       "viewyourtext": "Zòt pouvé wè ké kopyé kontni-a di <strong>zòt modifikasyon</strong> à sa paj.",
+       "protectedinterface": "Sa paj ka fourni tèks d'entèrfas pou lojisyèl-a asou sa wiki é sa protéjé pou évité abi-ya.\nPou ajouté oben modifyé dé amòrfwazaj asou tout wiki, souplé, itilizé [https://translatewiki.net/ translatewiki.net], projè-a di réjyonalizasyon di MediaWiki.",
+       "editinginterface": "<strong>Panga :</strong> zòt ka modifiyé oun paj itilizé pou kréyé tèks-a di lojisyèl.\nChanjman-yan asou sa paj ké répèrkité asou aparans di entèrfas itilizatò pou ròt itilizatò-ya di sa wiki.",
+       "translateinterface": "Pou ajouté oben modifyé dé amòrfwazaj pou tout wiki, souplé, itilizé [https://translatewiki.net/ translatewiki.net], projè-a di lokalizasyon lengwistik di MediaWiki.",
+       "cascadeprotected": "Sa paj protéjé kont modifikasyon-yan pas li sa transkliz pa {{PLURAL:$1|paj-a ki ka swiv, ki té protéjé|paj-ya ki ka swiv, ki té protéjé}} ké lòpsyon « protèksyon an kaskad » aktivé :\n$2",
+       "namespaceprotected": "Zòt pa gen pèrmisyon-an di modifyé paj-ya di lèspas di non « <strong>$1</strong> ».",
        "userlogin-yourname": "Non di itilizatò",
        "userlogin-yourname-ph": "Antré zòt non di itilizatò",
        "userlogin-yourpassword": "Mo di pas",
index aff9ec3..2a2468d 100644 (file)
        "userpage-userdoesnotexist-view": "חשבון המשתמש \"$1\" אינו רשום.",
        "blocked-notice-logextract": "{{GENDER:$1|המשתמש הזה חסום|המשתמשת הזו חסומה}} כרגע.\nהפעולה האחרונה ביומן החסימות מוצגת להלן:",
        "clearyourcache": "<strong>הערה:</strong> לאחר השמירה, ייתכן שיהיה צורך לנקות את זיכרון המטמון (cache) של הדפדפן כדי להבחין בשינויים.\n* <strong>פיירפוקס / ספארי:</strong> להחזיק את המקש <em>Shift</em> בעת לחיצה על <strong>טעינה מחדש</strong> (Reload), או ללחוץ על צירוף המקשים <em>Ctrl-F5</em> או <em>Ctrl-R</em> (במחשב מק: <em dir=\"ltr\">⌘-R</em>).\n* <strong>גוגל כרום:</strong> ללחוץ על צירוף המקשים <em>Ctrl-Shift-R</em> (במחשב מק: <em dir=\"ltr\">⌘-Shift-R</em>).\n* <strong>אינטרנט אקספלורר:</strong> להחזיק את המקש <em>Ctrl</em> בעת לחיצה על <strong>רענן</strong> (Refresh), או ללחוץ על צירוף המקשים <em>Ctrl-F5</em>.\n* <strong>אופרה:</strong> לפתוח <em>תפריט ← הגדרות</em> (במחשב מק: <em>Opera ← העדפות</em>) ואז ללחוץ על <em>פרטיות ואבטחה ← מחק היסטוריית גלישה ← Cached images and files</em>.",
-       "usercssyoucanpreview": "<strong>עצ×\94:</strong> ×\94שת×\9eש×\95 ×\91×\9bפת×\95ר \"{{int:showpreview}}\" ×\9b×\93×\99 ×\9c×\91×\97×\95×\9f ×\90ת ×\92×\99×\9c×\99×\95×\9f ×\94Ö¾CSS ×\94×\97×\93ש ×©×\9c×\9b×\9d לפני השמירה.",
-       "userjsonyoucanpreview": "<strong>עצ×\94:</strong> ×\94שת×\9eש×\95 ×\91×\9bפת×\95ר \"{{int:showpreview}}\" ×\9b×\93×\99 ×\9c×\91×\97×\95×\9f ×\90ת ×\93×£ ×\94Ö¾JSON ×\94×\97×\93ש ×©×\9c×\9b×\9d לפני השמירה.",
-       "userjsyoucanpreview": "<strong>עצ×\94:</strong> ×\94שת×\9eש×\95 ×\91×\9bפת×\95ר \"{{int:showpreview}}\" ×\9b×\93×\99 ×\9c×\91×\97×\95×\9f ×\90ת ×¡×§×¨×\99פ×\98 ×\94Ö¾JavaScript ×\94×\97×\93ש ×©×\9c×\9b×\9d לפני השמירה.",
-       "usercsspreview": "<strong>זִכרו שזו רק תצוגה מקדימה של גיליון ה־CSS שלכם.\nהוא עדיין לא נשמר!</strong>",
-       "userjsonpreview": "<strong>זִכרו שזו רק בדיקה/תצוגה מקדימה של הגדרות ה־JSON שלכם.\nהן עדיין לא נשמרו!</strong>",
-       "userjspreview": "<strong>זִכרו שזו רק בדיקה/תצוגה מקדימה של סקריפט ה־JavaScript שלכם.\nהוא עדיין לא נשמר!</strong>",
-       "sitecsspreview": "<strong>זִכרו שזו רק תצוגה מקדימה של גיליון ה־CSS הזה.\nהוא עדיין לא נשמר!</strong>",
-       "sitejsonpreview": "<strong>זִכרו שזו רק תצוגה מקדימה של הגדרות ה־JSON האלה.\nהן עדיין לא נשמרו!</strong>",
-       "sitejspreview": "<strong>זִכרו שזו רק תצוגה מקדימה של סקריפט ה־JavaScript הזה.\nהוא עדיין לא נשמר!</strong>",
-       "userinvalidconfigtitle": "<strong>אזהרה:</strong> העיצוב \"$1\" אינו קיים.\nדפי .css, דפי .json, ודפי .js מותאמים אישית משתמשים בכותרת עם אותיות קטנות – למשל, {{ns:user}}:דוגמה/vector.css ולא {{ns:user}}:דוגמה/Vector.css.",
+       "usercssyoucanpreview": "<strong>עצ×\94:</strong> ×\91×\90פשר×\95ת×\9a ×\9c×\94שת×\9eש ×\91×\9bפת×\95ר \"{{int:showpreview}}\" ×\9b×\93×\99 ×\9c×\91×\97×\95×\9f ×\90ת ×\92×\99×\9c×\99×\95×\9f ×\94Ö¾CSS ×\94×\97×\93ש ×©×\9c×\9a לפני השמירה.",
+       "userjsonyoucanpreview": "<strong>עצ×\94:</strong> ×\91×\90פשר×\95ת×\9a ×\9c×\94שת×\9eש ×\91×\9bפת×\95ר \"{{int:showpreview}}\" ×\9b×\93×\99 ×\9c×\91×\97×\95×\9f ×\90ת ×\93×£ ×\94Ö¾JSON ×\94×\97×\93ש ×©×\9c×\9a לפני השמירה.",
+       "userjsyoucanpreview": "<strong>עצ×\94:</strong> ×\91×\90פשר×\95ת×\9a ×\9c×\94שת×\9eש ×\91×\9bפת×\95ר \"{{int:showpreview}}\" ×\9b×\93×\99 ×\9c×\91×\97×\95×\9f ×\90ת ×¡×§×¨×\99פ×\98 ×\94Ö¾JavaScript ×\94×\97×\93ש ×©×\9c×\9a לפני השמירה.",
+       "usercsspreview": "<strong>זו רק תצוגה מקדימה של גיליון ה־CSS שלך.\nהוא עדיין לא נשמר!</strong>",
+       "userjsonpreview": "<strong>זו רק בדיקה/תצוגה מקדימה של הגדרות ה־JSON שלך.\nהן עדיין לא נשמרו!</strong>",
+       "userjspreview": "<strong>זו רק בדיקה/תצוגה מקדימה של סקריפט ה־JavaScript שלך.\nהוא עדיין לא נשמר!</strong>",
+       "sitecsspreview": "<strong>זו רק תצוגה מקדימה של גיליון ה־CSS הזה.\nהוא עדיין לא נשמר!</strong>",
+       "sitejsonpreview": "<strong>זו רק תצוגה מקדימה של הגדרות ה־JSON האלה.\nהן עדיין לא נשמרו!</strong>",
+       "sitejspreview": "<strong>זו רק תצוגה מקדימה של סקריפט ה־JavaScript הזה.\nהוא עדיין לא נשמר!</strong>",
+       "userinvalidconfigtitle": "<strong>אזהרה:</strong> העיצוב \"$1\" אינו קיים.\nדפי <span dir=\"ltr\">.css</span>, דפי <span dir=\"ltr\"><span dir=\"ltr\">.js</span>on</span> ודפי <span dir=\"ltr\">.js</span> מותאמים אישית משתמשים בכותרת עם אותיות קטנות – למשל, {{ns:user}}:דוגמה/vector.css ולא {{ns:user}}:דוגמה/Vector.css.",
        "updated": "(מעודכן)",
        "note": "'''הערה:'''",
        "previewnote": "<strong>{{GENDER:|זכור|זִכרי|זִכרו}} שזו רק תצוגה מקדימה.</strong>\nהשינויים {{GENDER:|שלך|שלך|שלכם}} עדיין לא נשמרו!",
        "apisandbox-dynamic-error-exists": "פרמטר בשם \"$1\" כבר קיים.",
        "apisandbox-deprecated-parameters": "פרמטרים מיושנים",
        "apisandbox-fetch-token": "מילוי אוטומטי של האסימון",
+       "apisandbox-add-multi": "הוספה",
        "apisandbox-submit-invalid-fields-title": "חלק מהשדות אינם תקינים",
        "apisandbox-submit-invalid-fields-message": "אנא תקנו את השדות המסומנים ונסו שוב.",
        "apisandbox-results": "תוצאות",
index 53be78f..73f35eb 100644 (file)
        "sp-contributions-newbies": "Ցույց տալ միայն նորաստեղծ հաշիվներից կատարված ներդրումները",
        "sp-contributions-newbies-sub": "Նոր մասնակցային հաշիվներից",
        "sp-contributions-newbies-title": "Նոր մասնակիցների ներդրումներ",
-       "sp-contributions-blocklog": "Արգելափակման տեղեկամատյան",
+       "sp-contributions-blocklog": "արգելափակման տեղեկամատյան",
        "sp-contributions-deleted": "մասնակցի ջնջված ներդրում",
-       "sp-contributions-uploads": "Բեռնումներ",
+       "sp-contributions-uploads": "բեռնումներ",
        "sp-contributions-logs": "տեղեկամատյաններ",
        "sp-contributions-talk": "քննարկում",
        "sp-contributions-userrights": "մասնակիցների իրավունքների կառավարում",
        "contribslink": "ներդրում",
        "emaillink": "ուղարկել էլ. նամակ",
        "autoblocker": "Դուք ավտոմատիկ արգելափակվել եք «$1» մասնակցի հետ ձեր IP-հասցեի համընկնելու պատճառով։ Նրա արգելափակման պատճառն է՝ «$2»։",
-       "blocklogpage": "Արգելափակման տեղեկամատյան",
+       "blocklogpage": "արգելափակման տեղեկամատյան",
        "blocklogentry": "արգելափակեց [[$1]] մասնակցին $2 տևողությամբ (Պատճառը՝ $3)",
        "reblock-logentry": "փոխեց [[$1]] մասնակցի արգելափակումը՝ դարձնելով $2 $3",
        "blocklogtext": "Սա մասնակիցների արգելափակման և արգելափակումից հանման տեղեկամատյանն է։\nԱվտոմատ կերպով արգելափակված IP-հասցեներն այստեղ ընդգրկված չեն։\nՏես [[Special:BlockList|այս պահին ակտիվ արգելափակումների ցանկը]]։",
        "special-characters-title-endash": "ո գծիկ (en dash)",
        "special-characters-title-emdash": "ա գծիկ (em dash)",
        "special-characters-title-minus": "հանածի նշան",
+       "mw-widgets-dateinput-no-date": "Ամսաթիվն ընտրված չէ",
        "mw-widgets-mediasearch-noresults": "Ոչինչ չի գտնվել",
+       "date-range-from": "Սկսած՝",
+       "date-range-to": "Մինչև՝",
        "authmanager-create-from-login": "Հաշիվ ստեղծելու համար, խնդրում ենք լրացնել ստորև դաշտերը"
 }
index fa6beaf..bee63bb 100644 (file)
        "revdelete-concurrent-change": "Impossibile modificare l'oggetto con data $1 $2 in quanto il suo stato è stato modificato da un altro utente mentre se ne tentava la modifica.",
        "revdelete-only-restricted": "Errore nel nascondere l'oggetto datato $1, $2: non è possibile nascondere gli oggetti alla vista degli amministratori senza selezionare almeno un'altra delle opzioni di rimozione.",
        "revdelete-reason-dropdown": "* Motivazioni più comuni per la cancellazione\n** Violazione di copyright\n** Commenti o informazioni personali inappropriate\n** Nome utente inappropriato\n** Informazione potenzialmente diffamatoria",
-       "revdelete-otherreason": "Altra motivazione o motivazione aggiuntiva:",
+       "revdelete-otherreason": "Altri motivi/dettagli:",
        "revdelete-reasonotherlist": "Altra motivazione",
        "revdelete-edit-reasonlist": "Modifica le motivazioni per la cancellazione",
        "revdelete-offender": "Autore della versione:",
        "filedelete-success-old": "La versione del file '''[[Media:$1|$1]]''' del $2, $3  è stata cancellata.",
        "filedelete-nofile": "Non esiste un file '''$1'''.",
        "filedelete-nofile-old": "In archivio non ci sono versioni di '''$1''' con le caratteristiche indicate",
-       "filedelete-otherreason": "Altra motivazione o motivazione aggiuntiva:",
+       "filedelete-otherreason": "Altri motivi/dettagli:",
        "filedelete-reason-otherlist": "Altra motivazione",
        "filedelete-reason-dropdown": "*Motivazioni più comuni per la cancellazione\n** Violazione di copyright\n** File duplicato",
        "filedelete-edit-reasonlist": "Modifica le motivazioni per la cancellazione",
        "deletionlog": "cancellazioni",
        "reverted": "Ripristinata la versione precedente",
        "deletecomment": "Motivo:",
-       "deleteotherreason": "Altra motivazione o motivazione aggiuntiva:",
+       "deleteotherreason": "Altri motivi/dettagli:",
        "deletereasonotherlist": "Altra motivazione",
        "deletereason-dropdown": "* Motivazioni più comuni per la cancellazione\n** Spam\n** Vandalismo\n** Violazione di copyright\n** Richiesta dell'autore\n** Redirect rotto",
        "delete-edit-reasonlist": "Modifica i motivi di cancellazione",
index 966f392..f58f918 100644 (file)
        "recentchangeslinked-page": "താളിന്റെ പേര്:",
        "recentchangeslinked-to": "തന്നിരിക്കുന്ന താളിലെ മാറ്റങ്ങൾക്കു പകരം ബന്ധപ്പെട്ട താളുകളിലെ മാറ്റങ്ങൾ കാണിക്കുക",
        "recentchanges-page-added-to-category": "[[:$1]] വർഗ്ഗത്തിലേക്ക് ചേർത്തിരിക്കുന്നു",
-       "recentchanges-page-added-to-category-bundled": "[[:$1]] à´¤à´¾à´³àµ\81à´\82 à´\92à´ªàµ\8dà´ªà´\82 [[Special:WhatLinksHere/$1|{{PLURAL:$2|മറàµ\8dà´±àµ\8aà´°àµ\81 à´¤à´¾à´³àµ\81à´\82|$2 à´¤à´¾à´³àµ\81à´\95à´³àµ\81à´\82}}]] à´µàµ¼à´\97àµ\8dà´\97à´¤àµ\8dതിലàµ\87à´\95àµ\8dà´\95àµ\8d à´\9aàµ\87ർതàµ\8dതിരിà´\95àµ\8dà´\95àµ\81à´¨àµ\8dà´¨àµ\81",
+       "recentchanges-page-added-to-category-bundled": "[[:$1]] à´¤à´¾àµ¾ à´µàµ¼à´\97àµ\8dà´\97à´¤àµ\8dതിലàµ\87à´\95àµ\8dà´\95àµ\8d à´\9aàµ\87ർതàµ\8dതിരിà´\95àµ\8dà´\95àµ\81à´¨àµ\8dà´¨àµ\81, [[Special:WhatLinksHere/$1|à´\88 à´¤à´¾àµ¾ à´®à´±àµ\8dà´±àµ\8d à´¤à´¾à´³àµ\81à´\95ളിൽ à´\89ൾപàµ\8dà´ªàµ\86à´\9fàµ\81à´¤àµ\8dതിയിരിà´\95àµ\8dà´\95àµ\81à´¨àµ\8dà´¨àµ\81]]",
        "recentchanges-page-removed-from-category": "[[:$1]] വർഗ്ഗത്തിൽ നിന്ന് നീക്കംചെയ്തു",
-       "recentchanges-page-removed-from-category-bundled": "[[:$1]] à´¤à´¾à´³àµ\81à´\82 à´\92à´ªàµ\8dà´ªà´\82 {{PLURAL:$2|മറàµ\8dà´±àµ\8aà´°àµ\81 à´¤à´¾à´³àµ\81à´\82|$2 à´¤à´¾à´³àµ\81à´\95à´³àµ\81à´\82}} à´µàµ¼à´\97àµ\8dà´\97à´¤àµ\8dതിൽ à´¨à´¿à´¨àµ\8dà´¨àµ\8d à´¨àµ\80à´\95àµ\8dà´\95à´\82à´\9aàµ\86à´¯àµ\8dതിരിà´\95àµ\8dà´\95àµ\81à´¨àµ\8dà´¨àµ\81",
+       "recentchanges-page-removed-from-category-bundled": "[[:$1]] à´¤à´¾àµ¾ à´µàµ¼à´\97àµ\8dà´\97à´¤àµ\8dതിൽ à´¨à´¿à´¨àµ\8dà´¨àµ\8d à´¨àµ\80à´\95àµ\8dà´\95à´\82à´\9aàµ\86à´¯àµ\8dതിരിà´\95àµ\8dà´\95àµ\81à´¨àµ\8dà´¨àµ\81, [[Special:WhatLinksHere/$1|à´\88 à´¤à´¾àµ¾ à´®à´±àµ\8dà´±àµ\8d à´¤à´¾à´³àµ\81à´\95ളിൽ à´\89ൾപàµ\8dà´ªàµ\86à´\9fàµ\81à´¤àµ\8dതിയിരിà´\95àµ\8dà´\95àµ\81à´¨àµ\8dà´¨àµ\81]]",
        "autochange-username": "മീഡിയവിക്കി സ്വയംപ്രവർത്തിത മാറ്റം",
        "upload": "അപ്‌ലോഡ്‌",
        "uploadbtn": "പ്രമാണം അപ്‌ലോഡ് ചെയ്യുക",
        "uploaded-script-svg": "അപ്‌ലോഡ് ചെയ്ത എസ്.വി.ജി. പ്രമാണത്തിൽ സ്ക്രിപ്റ്റ് ചെയ്യാവുന്ന ഭാഗമായ \"$1\" കണ്ടെത്തി.",
        "uploaded-hostile-svg": "അപ്‌ലോഡ് ചെയ്ത എസ്.വി.ജി. പ്രമാണത്തിൽ സുരക്ഷിതമല്ലാത്ത സി.എസ്.എസ്. സ്റ്റൈൽ ഭാഗം കണ്ടെത്താനായി.",
        "uploaded-event-handler-on-svg": "എസ്.വി.ജി. പ്രമാണങ്ങളിൽ എവന്റ്-ഹാൻഡ്‌ലർ ആട്രിബ്യൂട്ടുകൾ <code>$1=\"$2\"</code>  എന്ന് സജ്ജീകരിച്ചിരിക്കുന്നവ അനുവദിച്ചിട്ടില്ല.",
-       "uploaded-href-unsafe-target-svg": "à´\85à´ªàµ\8dâ\80\8cà´²àµ\8bà´¡àµ\8d à´\9aàµ\86à´¯àµ\8dà´¤ à´\8eà´¸àµ\8d.വി.à´\9cà´¿. à´ªàµ\8dരമാണതàµ\8dതിൽ à´¸àµ\81à´°à´\95àµ\8dഷിതമലàµ\8dലാതàµ\8dà´¤ à´²à´\95àµ\8dà´·àµ\8dയമായ <code>&lt;$1 $2=\"$3\"&gt;</code> à´\95à´£àµ\8dà´\9fàµ\86à´¤àµ\8dതി.",
+       "uploaded-href-unsafe-target-svg": "à´¸àµ\81à´°à´\95àµ\8dഷിതമലàµ\8dലാതàµ\8dà´¤ à´¡àµ\87à´±àµ\8dറയിലàµ\87à´\95àµ\8dà´\95àµ\81à´³àµ\8dà´³ à´\95à´£àµ\8dണി à´\95à´£àµ\8dà´\9fàµ\86à´¤àµ\8dതി: à´¯àµ\81.à´\86ർ.à´\90. à´²à´\95àµ\8dà´·àµ\8dയമായ <code>&lt;$1 $2=\"$3\"&gt;</code> à´\85à´ªàµ\8dâ\80\8cà´²àµ\8bà´¡àµ\8d à´\9aàµ\86à´¯àµ\8dà´¤ à´\8eà´¸àµ\8d.വി.à´\9cà´¿. à´ªàµ\8dരമാണതàµ\8dതിൽ à´\89à´£àµ\8dà´\9fàµ\8d.",
        "uploaded-animate-svg": "അപ്‌ലോഡ് ചെയ്ത എസ്.വി.ജി. പ്രമാണത്തിൽ <code>&lt;$1 $2=\"$3\"&gt;</code> ആട്രിബ്യൂട്ട് ഉപയോഗിച്ച് href മാറ്റിയേക്കാവുന്ന \"animate\" റ്റാഗായ <code>&lt;$1 $2=\"$3\"&gt;</code> കണ്ടെത്തി.",
        "uploaded-setting-event-handler-svg": "അപ്‌ലോഡ് ചെയ്ത എസ്.വി.ജി. പ്രമാണത്തിൽ <code>&lt;$1 $2=\"$3\"&gt;</code> കണ്ടെത്തി, ഇവന്റ്-കൈകാര്യ സജ്ജീകരണ ആട്രിബ്യൂട്ടുകൾ തടഞ്ഞിരിക്കുന്നു.",
        "uploaded-setting-href-svg": "മാതൃഘടകത്തിലേക്ക് \"href\" ആട്രിബ്യൂട്ട് ചേർക്കാൻ \"set\" പതാക ഉപയോഗിക്കുന്നത് തടഞ്ഞിരിക്കുന്നു.",
        "backend-fail-read": "$1 എന്ന പ്രമാണം വായിക്കാൻ കഴിഞ്ഞില്ല.",
        "backend-fail-create": "$1 എന്ന പ്രമാണം സൃഷ്ടിക്കാൻ കഴിഞ്ഞില്ല.",
        "backend-fail-maxsize": "{{PLURAL:$2|$2 ബൈറ്റ്സിലും|$2 ബൈറ്റിലും}} വലുതാണെന്ന കാരണത്താൽ $1 എന്ന പ്രമാണം സൃഷ്ടിക്കാൻ കഴിഞ്ഞില്ല.",
-       "backend-fail-readonly": "സംഭരണ ബാക്കെൻഡ് \"$1\" ഇപ്പോൾ കാണൽ-മാത്രം (read-only) രീതിയിലാണ്. നൽകിയിരിക്കുന്ന കാരണം: \"''$2''\"",
+       "backend-fail-readonly": "സംഭരണ ബാക്കെൻഡ് \"$1\" ഇപ്പോൾ കാണൽ-മാത്രം (read-only) രീതിയിലാണ്. നൽകിയിരിക്കുന്ന കാരണം: <em>$2</em>",
        "backend-fail-synced": "ആന്തരിക ശേഖരണ ബാക്കെൻഡിൽ പ്രമാണം \"$1\" അസ്ഥിരാവസ്ഥയിലാണുള്ളത്",
        "backend-fail-connect": "\"$1\"  ശേഖരണ ബാക്കെൻഡുമായി ബന്ധപ്പെടാൻ കഴിഞ്ഞില്ല.",
        "backend-fail-internal": "\"$1\" എന്ന സ്റ്റോറേജ് ബാക്കെൻഡിൽ അപരിചിതമായ പിഴവ് സംഭവിച്ചു.",
        "uploadstash-summary": "അപ്‌ലോഡ് ചെയ്യപ്പെട്ടതും (അല്ലെങ്കിൽ ചെയ്തുകൊണ്ടിരിക്കുന്നതും) അതേസമയം വിക്കിയിൽ പ്രസിദ്ധീകരിക്കാത്തതുമായ പ്രമാണങ്ങളിലേയ്ക്ക് എത്താനുള്ള സൗകര്യം ഈ താൾ നൽകുന്നു. ഈ പ്രമാണങ്ങൾ അപ്‌ലോഡ് ചെയ്ത ആൾക്കൊഴികെ മറ്റാർക്കും കാണാവുന്നതല്ല.",
        "uploadstash-clear": "രഹസ്യമാക്കിയ പ്രമാണങ്ങൾ ശൂന്യമാക്കുക",
        "uploadstash-nofiles": "താങ്കൾക്ക് രഹസ്യമാക്കിയ പ്രമാണങ്ങൾ ഒന്നുമില്ല.",
-       "uploadstash-badtoken": "à´ªàµ\8dà´°à´µàµ\83à´¤àµ\8dതി à´µà´¿à´\9cà´¯à´\95രമായിരàµ\81à´¨àµ\8dനിലàµ\8dà´², താങ്കളുടെ തിരുത്തുവാനുള്ള അവകാശങ്ങൾ ചിലപ്പോൾ കാലഹരണപ്പെട്ടിട്ടുണ്ടാകാം. വീണ്ടും ശ്രമിക്കുക.",
+       "uploadstash-badtoken": "à´ªàµ\8dà´°à´µàµ\83à´¤àµ\8dതി à´ªà´°à´¾à´\9cയപàµ\8dà´ªàµ\86à´\9fàµ\8dà´\9fàµ\81, താങ്കളുടെ തിരുത്തുവാനുള്ള അവകാശങ്ങൾ ചിലപ്പോൾ കാലഹരണപ്പെട്ടിട്ടുണ്ടാകാം. വീണ്ടും ശ്രമിക്കുക.",
        "uploadstash-errclear": "പ്രമാണങ്ങൾ ശൂന്യമാക്കൽ പരാജയപ്പെട്ടു.",
        "uploadstash-refresh": "പ്രമാണങ്ങളുടെ പട്ടിക പുതുക്കുക",
        "uploadstash-thumbnail": "ലഘുചിത്രം കാണുക",
        "apihelp-no-such-module": "ഘടകം \"$1\" കണ്ടെത്താനായില്ല.",
        "apisandbox": "എ.പി.ഐ. എഴുത്തുകളരി",
        "apisandbox-api-disabled": "ഈ സൈറ്റിൽ എ.പി.ഐ. പ്രവർത്തനരഹിതമാക്കിയിരിക്കുന്നു.",
-       "apisandbox-intro": "'''മീഡിയവിക്കി വെബ്‌ സെർവീസ് എ.പി.ഐ.'''യിൽ പരീക്ഷണങ്ങൾ നടത്താൻ ഈ താൾ ഉപയോഗിക്കുക.\nഎ.പി.ഐ.യുടെ ഉപയോഗത്തെക്കുറിച്ചുള്ള കൂടുതൽ വിവരങ്ങൾക്കായി [https://www.mediawiki.org/wiki/API:Main_page the എ.പി.ഐ. സഹായം] പരിശോധിക്കുക. ഉദാഹരണം: [https://www.mediawiki.org/wiki/API#A_simple_example പ്രധാന താളിന്റെ ഉള്ളടക്കം എടുക്കുക]. കൂടുതൽ ഉദാഹരണങ്ങൾക്കായി പ്രവൃത്തി തിരഞ്ഞെടുക്കുക.\n\nഇതൊരു പരീക്ഷണകളരിയാണെങ്കിലും ഇവിടെ ചെയ്യുന്നവ വിക്കിയിൽ മാറ്റങ്ങൾ വരുത്തിയേക്കാമെന്ന് ഓർക്കുക.",
+       "apisandbox-intro": "<strong>മീഡിയവിക്കി വെബ്‌ സെർവീസ് എ.പി.ഐ.</strong>യിൽ പരീക്ഷണങ്ങൾ നടത്താൻ ഈ താൾ ഉപയോഗിക്കുക.\nഎ.പി.ഐ.യുടെ ഉപയോഗത്തെക്കുറിച്ചുള്ള കൂടുതൽ വിവരങ്ങൾക്കായി [[mw:API:Main page|എ.പി.ഐ. സഹായം]] പരിശോധിക്കുക. ഉദാഹരണം: [https://www.mediawiki.org/wiki/API#A_simple_example പ്രധാന താളിന്റെ ഉള്ളടക്കം എടുക്കുക]. കൂടുതൽ ഉദാഹരണങ്ങൾക്കായി പ്രവൃത്തി തിരഞ്ഞെടുക്കുക.\n\nഇതൊരു പരീക്ഷണകളരിയാണെങ്കിലും ഇവിടെ ചെയ്യുന്നവ വിക്കിയിൽ മാറ്റങ്ങൾ വരുത്തിയേക്കാമെന്ന് ഓർക്കുക.",
        "apisandbox-unfullscreen": "താൾ പ്രദർശിപ്പിക്കുക",
        "apisandbox-submit": "അഭ്യർത്ഥിക്കുക",
        "apisandbox-reset": "ശൂന്യമാക്കുക",
        "version-poweredby-others": "മറ്റുള്ളവർ",
        "version-poweredby-translators": "പരിഭാഷാവിക്കിയിലെ പരിഭാഷകർ",
        "version-credits-summary": "[[Special:Version|മീഡിയവിക്കിയ്ക്ക്]] നൽകിയ സംഭാവനകളുടെ പേരിൽ താഴെക്കൊടുക്കുന്നവർക്ക് ഞങ്ങൾ നന്ദി പറയുന്നു.",
-       "version-license-info": "മീഡിയവിക്കി ഒരു സ്വതന്ത്ര സോഫ്റ്റ്‌വേറാണ്; സ്വതന്ത്ര സോഫ്റ്റ്‌വേർ ഫൗണ്ടേഷൻ പ്രസിദ്ധീകരിച്ചിട്ടുള്ള ഗ്നു സാർവ്വജനിക അനുവാദപത്രത്തിന്റെ പതിപ്പ് 2 പ്രകാരമോ, അല്ലെങ്കിൽ (താങ്കളുടെ ഇച്ഛാനുസരണം) പിന്നീട് പ്രസിദ്ധീകരിച്ച ഏതെങ്കിലും പതിപ്പ് പ്രകാരമോ താങ്കൾക്കിത് പുനർവിതരണം ചെയ്യാനും ഒപ്പം/അല്ലെങ്കിൽ മാറ്റങ്ങൾ വരുത്താനും സാധിക്കുന്നതാണ്.\n\nമീഡിയവിക്കി താങ്കൾക്കുപകരിക്കുമെന്ന പ്രതീക്ഷയോടെയാണ് വിതരണം ചെയ്യുന്നത്, പക്ഷേ യാതൊരു ഗുണമേന്മോത്തരവാദിത്തവും വഹിക്കുന്നില്ല; വ്യാപാരയോഗ്യമെന്നോ പ്രത്യേക ഉപയോഗത്തിന് അനുയോജ്യമെന്നോ ഉള്ള യാതൊരു ഗുണമേന്മോത്തരവാദിത്തവും ഇത് ഉൾക്കൊള്ളുന്നില്ല. കൂടുതൽ വിവരങ്ങൾക്ക് ഗ്നു സാർവ്വജനിക അനുവാദപത്രം കാണുക.\n\nഈ പ്രോഗ്രാമിനൊപ്പം [{{SERVER}}{{SCRIPTPATH}}/COPYING ഗ്നു സാർവ്വജനിക അനുവാദപത്രത്തിന്റെ ഒരു പകർപ്പ്] താങ്കൾക്ക് ലഭിച്ചിരിക്കും; ഇല്ലെങ്കിൽ Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA എന്ന വിലാസത്തിലെഴുതുക അല്ലെങ്കിൽ [//www.gnu.org/licenses/old-licenses/gpl-2.0.html അനുവാദപത്രം ഓൺലൈനായി വായിക്കുക].",
+       "version-license-info": "മീഡിയവിക്കി ഒരു സ്വതന്ത്ര സോഫ്റ്റ്‌വേറാണ്; സ്വതന്ത്ര സോഫ്റ്റ്‌വേർ ഫൗണ്ടേഷൻ പ്രസിദ്ധീകരിച്ചിട്ടുള്ള ഗ്നു സാർവ്വജനിക അനുവാദപത്രത്തിന്റെ പതിപ്പ് 2 പ്രകാരമോ, അല്ലെങ്കിൽ (താങ്കളുടെ ഇച്ഛാനുസരണം) പിന്നീട് പ്രസിദ്ധീകരിച്ച ഏതെങ്കിലും പതിപ്പ് പ്രകാരമോ താങ്കൾക്കിത് പുനർവിതരണം ചെയ്യാനും ഒപ്പം/അല്ലെങ്കിൽ മാറ്റങ്ങൾ വരുത്താനും സാധിക്കുന്നതാണ്.\n\nമീഡിയവിക്കി താങ്കൾക്കുപകരിക്കുമെന്ന പ്രതീക്ഷയോടെയാണ് വിതരണം ചെയ്യുന്നത്, പക്ഷേ <em>യാതൊരു ഗുണമേന്മോത്തരവാദിത്തവും വഹിക്കുന്നില്ല</em>; <strong>വ്യാപാരയോഗ്യമെന്നോ പ്രത്യേക ഉപയോഗത്തിന് അനുയോജ്യമെന്നോ</strong> ഉള്ള യാതൊരു ഗുണമേന്മോത്തരവാദിത്തവും ഇത് ഉൾക്കൊള്ളുന്നില്ല. കൂടുതൽ വിവരങ്ങൾക്ക് ഗ്നു സാർവ്വജനിക അനുവാദപത്രം കാണുക.\n\nഈ പ്രോഗ്രാമിനൊപ്പം [{{SERVER}}{{SCRIPTPATH}}/COPYING ഗ്നു സാർവ്വജനിക അനുവാദപത്രത്തിന്റെ ഒരു പകർപ്പ്] താങ്കൾക്ക് ലഭിച്ചിരിക്കും; ഇല്ലെങ്കിൽ സ്വതന്ത്ര സോഫ്റ്റ്‌വേർ ഫൗണ്ടേഷൻ.ഇൻക്., 51 ഫ്രാങ്ക്‌ലിൻ തെരുവ്, അഞ്ചാം നില, ബോസ്റ്റൺ, എം.എ. 02110-1301, അമേരിക്കൻ ഐക്യനാടുകൾ എന്ന വിലാസത്തിലെഴുതുക അല്ലെങ്കിൽ [//www.gnu.org/licenses/old-licenses/gpl-2.0.html അനുവാദപത്രം ഓൺലൈനായി വായിക്കുക].",
        "version-software": "ഇൻസ്റ്റോൾ ചെയ്ത സോഫ്റ്റ്‌വെയർ",
        "version-software-product": "ഉല്പന്നം",
        "version-software-version": "പതിപ്പ്",
        "tags-create-reason": "കാരണം:",
        "tags-create-submit": "സൃഷ്ടിക്കുക",
        "tags-create-no-name": "റ്റാഗിന്റെ പേര് വ്യക്തമാക്കേണ്ടതുണ്ട്.",
-       "tags-create-invalid-chars": "à´\9fà´¾à´\97à´¿à´¨àµ\8dà´±àµ\86 à´ªàµ\87രിൽ à´\85à´²àµ\8dപവിരാമà´\99àµ\8dà´\99à´³àµ\8b (<code>,</code>), à´®àµ\81à´¨àµ\8dà´¨àµ\8bà´\9fàµ\8dà´\9fàµ\81à´³àµ\8dà´³ à´¸àµ\8dലാഷàµ\8b (<code>/</code>) à´\89à´£àµ\8dà´\9fായിരിà´\95àµ\8dà´\95ാൻ à´ªà´¾à´\9fàµ\81à´³àµ\8dളതലàµ\8dà´².",
+       "tags-create-invalid-chars": "à´\9fà´¾à´\97à´¿à´¨àµ\8dà´±àµ\86 à´ªàµ\87രിൽ à´\85à´²àµ\8dപവിരാമà´\99àµ\8dà´\99à´³àµ\8b (<code>,</code>), à´ªàµ\88à´ªàµ\8dà´ªàµ\81à´\95à´³àµ\8b (<code>|</code>), à´®àµ\81à´¨àµ\8dà´¨àµ\8bà´\9fàµ\8dà´\9fàµ\81à´³àµ\8dà´³ à´¸àµ\8dലാഷàµ\8b (<code>/</code>) à´\89à´£àµ\8dà´\9fാവരàµ\81à´¤àµ\8d.",
        "tags-create-invalid-title-chars": "ടാഗിന്റെ പേരിൽ താളിന്റെ തലക്കെട്ടിൽ ഉൾപ്പെടുത്താൻ പാടില്ലാത്ത അക്ഷരങ്ങളൊന്നുമുണ്ടാവാൻ പാടില്ല.",
        "tags-create-already-exists": "\"$1\" എന്ന ടാഗ് നിലവിലുണ്ട്.",
        "tags-create-warnings-above": "\"$1\" എന്ന ടാഗ് സൃഷ്ടിക്കാൻ ശ്രമിക്കുമ്പോൾ താഴെക്കൊടുത്തിരിക്കുന്ന {{PLURAL:$2|മുന്നറിയിപ്പ്|മുന്നറിയിപ്പുകൾ}} വന്നു:",
        "tags-delete-not-allowed": "അനുബന്ധം വ്യക്തമായി അനുവദിക്കുന്നില്ലെങ്കിൽ, അനുബന്ധങ്ങൾ വഴി നിർവ്വചിക്കുന്ന ടാഗുകൾ മായ്ക്കാനാവുകയില്ല.",
        "tags-delete-not-found": "\"$1\" എന്ന ടാഗ് നിലവിലില്ല.",
        "tags-delete-too-many-uses": "\"$1\" എന്ന ടാഗ് {{PLURAL:$2|ഒന്നിലധികം നാൾപ്പതിപ്പുകളിൽ|$2 എണ്ണത്തിലധികം നാൾപ്പതിപ്പുകളിൽ}} ഉപയോഗിക്കുന്നു, അതിനാൽ അത് മായ്ക്കാനാവില്ല.",
-       "tags-delete-warnings-after-delete": "\"$1\" à´\8eà´¨àµ\8dà´¨ à´\9fà´¾à´\97àµ\8d à´µà´¿à´\9cà´¯à´\95രമായി à´®à´¾à´¯àµ\8dà´\9aàµ\8dà´\9aà´¿à´°à´¿à´\95àµ\8dà´\95àµ\81à´¨àµ\8dà´¨àµ\81, à´ªà´\95àµ\8dà´·àµ\87 à´\87നിà´\95àµ\8dà´\95àµ\8aà´\9fàµ\81à´\95àµ\8dà´\95àµ\81à´¨àµ\8dà´¨ {{PLURAL:$2|à´®àµ\81à´¨àµ\8dനറിയിപàµ\8dà´ªàµ\8d|à´®àµ\81à´¨àµ\8dനറിയിപàµ\8dà´ªàµ\81à´\95ൾ}} à´\89à´£àµ\8dà´\9fായി:",
+       "tags-delete-warnings-after-delete": "\"$1\" എന്ന ടാഗ് മായ്ച്ചിരിക്കുന്നു, പക്ഷേ ഇനിക്കൊടുക്കുന്ന {{PLURAL:$2|മുന്നറിയിപ്പ്|മുന്നറിയിപ്പുകൾ}} ഉണ്ടായി:",
        "tags-activate-title": "ടാഗ് സജ്ജമാക്കുക",
        "tags-activate-question": "താങ്കൾ, \"$1\" എന്ന ടാഗ് പ്രവർത്തനക്ഷമമാക്കാൻ പോവുകയാണ്.",
        "tags-activate-reason": "കാരണം:",
index 6d0e8b2..50bc3c7 100644 (file)
        "feedback-thanks": "Хвала! Ваша повратна информација је постављена на страницу „[$2 $1]“.",
        "feedback-thanks-title": "Хвала вам!",
        "feedback-useragent": "Кориснички агент:",
-       "searchsuggest-search": "Ð\9fÑ\80еÑ\82Ñ\80ажи Ð¿Ñ\80оÑ\98екаÑ\82 {{SITENAME}}",
+       "searchsuggest-search": "Ð\9fÑ\80еÑ\82Ñ\80ага",
        "searchsuggest-containing": "садржи...",
        "api-error-badtoken": "Унутрашња грешка: неисправан жетон.",
        "api-error-emptypage": "Стварање нових празних страница није дозвољено.",
index 3dcacd8..48d8c6b 100644 (file)
        "wrongpasswordempty": "รหัสผ่านที่กรอกว่าง\nโปรดลองอีกครั้ง",
        "passwordtooshort": "รหัสผ่านต้องมีอย่างน้อย $1 อักขระ",
        "passwordtoolong": "รหัสผ่านยาวกว่า $1 อักขระไม่ได้",
-       "passwordtoopopular": "à¹\83à¸\8aà¹\89รหัสà¸\9cà¹\88าà¸\99à¸\97ีà¹\88มีà¸\9cูà¹\89à¹\80ลือà¸\81à¸\97ัà¹\88วà¹\84à¸\9bà¹\84มà¹\88à¹\84à¸\94à¹\89 à¸\81รุà¸\93าà¹\80ลือà¸\81รหัสà¸\9cà¹\88าà¸\99à¸\97ีà¹\88มีà¸\9cูà¹\89à¹\83à¸\8aà¹\89à¸\99à¹\89อยกว่านี้",
+       "passwordtoopopular": "à¹\83à¸\8aà¹\89รหัสà¸\9cà¹\88าà¸\99à¸\97ีà¹\88มีà¸\9cูà¹\89à¹\80ลือà¸\81à¸\97ัà¹\88วà¹\84à¸\9bà¹\84มà¹\88à¹\84à¸\94à¹\89 à¸\81รุà¸\93าà¹\80ลือà¸\81รหัสà¸\9cà¹\88าà¸\99à¸\97ีà¹\88à¸\84าà¸\94à¹\80à¸\94าà¹\84à¸\94à¹\89ยาà¸\81กว่านี้",
        "password-name-match": "รหัสผ่านต้องต่างจากชื่อผู้ใช้",
        "password-login-forbidden": "ห้ามใช้ชื่อผู้ใช้และรหัสผ่านนี้",
        "mailmypassword": "ตั้งรหัสผ่านใหม่",
        "savechanges": "บันทึกการเปลี่ยนแปลง",
        "publishpage": "เผยแพร่หน้า",
        "publishchanges": "เผยแพร่การเปลี่ยนแปลง",
+       "savearticle-start": "บันทึกหน้า…",
+       "savechanges-start": "บันทึกการเปลี่ยนแปลง…",
+       "publishpage-start": "เผยแพร่หน้า…",
+       "publishchanges-start": "เผยแพร่การเปลี่ยนแปลง…",
        "preview": "ตัวอย่าง",
        "showpreview": "แสดงตัวอย่าง",
        "showdiff": "แสดงการเปลี่ยนแปลง",
index a21bc73..33cc0ca 100644 (file)
@@ -87,15 +87,14 @@ class CleanupPreferences extends Maintenance {
                // Remove unknown preferences. Special-case gadget- and userjs- as we can't
                // control those names.
                if ( $unknown ) {
-                       $this->deleteByWhere(
-                               $dbw,
-                               'Dropping unknown preferences',
-                               [
-                                       'up_property NOT' . $dbw->buildLike( 'gadget-', $dbw->anyString() ),
-                                       'up_property NOT' . $dbw->buildLike( 'userjs-', $dbw->anyString() ),
-                                       'up_property NOT IN (' . $dbw->makeList( array_keys( $wgDefaultUserOptions ) ) . ')',
-                               ]
-                       );
+                       $where = [
+                               'up_property NOT' . $dbw->buildLike( 'gadget-', $dbw->anyString() ),
+                               'up_property NOT' . $dbw->buildLike( 'userjs-', $dbw->anyString() ),
+                               'up_property NOT IN (' . $dbw->makeList( array_keys( $wgDefaultUserOptions ) ) . ')',
+                       ];
+                       // Allow extensions to add to the where clause to prevent deletion of their own prefs.
+                       Hooks::run( 'DeleteUnknownPreferences', [ &$where, $dbw ] );
+                       $this->deleteByWhere( $dbw, 'Dropping unknown preferences', $where );
                }
 
                // Something something phase 3
index 4ac985e..a770c91 100644 (file)
@@ -4,9 +4,9 @@ LANGUAGE plpgsql AS
 $mw$
 BEGIN
 IF TG_OP = 'INSERT' THEN
-  NEW.titlevector = to_tsvector('default',REPLACE(NEW.page_title,'/',' '));
+  NEW.titlevector = to_tsvector(REPLACE(NEW.page_title,'/',' '));
 ELSIF NEW.page_title != OLD.page_title THEN
-  NEW.titlevector := to_tsvector('default',REPLACE(NEW.page_title,'/',' '));
+  NEW.titlevector := to_tsvector(REPLACE(NEW.page_title,'/',' '));
 END IF;
 RETURN NEW;
 END;
index 271071b..d9429bc 100644 (file)
@@ -687,7 +687,6 @@ CREATE INDEX job_cmd_namespace_title ON job (job_cmd, job_namespace, job_title);
 CREATE INDEX job_timestamp_idx ON job (job_timestamp);
 
 -- Tsearch2 2 stuff. Will fail if we don't have proper access to the tsearch2 tables
--- Version 8.3 or higher only. Previous versions would need another parmeter for to_tsvector.
 -- Make sure you also change patch-tsearch2funcs.sql if the funcs below change.
 
 ALTER TABLE page ADD titlevector tsvector;
@@ -723,9 +722,6 @@ $mw$;
 CREATE TRIGGER ts2_page_text BEFORE INSERT OR UPDATE ON pagecontent
   FOR EACH ROW EXECUTE PROCEDURE ts2_page_text();
 
--- These are added by the setup script due to version compatibility issues
--- If using 8.1, we switch from "gin" to "gist"
-
 CREATE INDEX ts2_page_title ON page USING gin(titlevector);
 CREATE INDEX ts2_page_text ON pagecontent USING gin(textvector);
 
index 0d2b788..ffba861 100644 (file)
@@ -1539,6 +1539,11 @@ abstract class MediaWikiTestCase extends PHPUnit\Framework\TestCase {
                                        $db->delete( $tbl, '*', __METHOD__ );
                                }
 
+                               if ( $db->getType() === 'postgres' ) {
+                                       // Reset the table's sequence too.
+                                       $db->resetSequenceForTable( $tbl, __METHOD__ );
+                               }
+
                                if ( $tbl === 'page' ) {
                                        // Forget about the pages since they don't
                                        // exist in the DB.
index 719a3bf..7d6906c 100644 (file)
@@ -117,7 +117,10 @@ class RevisionStoreDbTest extends MediaWikiTestCase {
                                'trxProfiler' => new TransactionProfiler(),
                                'connLogger' => new \Psr\Log\NullLogger(),
                                'queryLogger' => new \Psr\Log\NullLogger(),
-                               'errorLogger' => new \Psr\Log\NullLogger(),
+                               'errorLogger' => function () {
+                               },
+                               'deprecationLogger' => function () {
+                               },
                                'type' => 'test',
                                'dbname' => $dbName,
                                'tablePrefix' => $dbPrefix,
diff --git a/tests/phpunit/includes/db/DatabasePostgresTest.php b/tests/phpunit/includes/db/DatabasePostgresTest.php
new file mode 100644 (file)
index 0000000..5c2aa2b
--- /dev/null
@@ -0,0 +1,177 @@
+<?php
+
+use Wikimedia\Rdbms\IDatabase;
+use Wikimedia\Rdbms\DatabasePostgres;
+use Wikimedia\ScopedCallback;
+use Wikimedia\TestingAccessWrapper;
+
+/**
+ * @group Database
+ */
+class DatabasePostgresTest extends MediaWikiTestCase {
+
+       private function doTestInsertIgnore() {
+               $reset = new ScopedCallback( function () {
+                       if ( $this->db->explicitTrxActive() ) {
+                               $this->db->rollback( __METHOD__ );
+                       }
+                       $this->db->query( 'DROP TABLE IF EXISTS ' . $this->db->tableName( 'foo' ) );
+               } );
+
+               $this->db->query(
+                       "CREATE TEMPORARY TABLE {$this->db->tableName( 'foo' )} (i INTEGER NOT NULL PRIMARY KEY)"
+               );
+               $this->db->insert( 'foo', [ [ 'i' => 1 ], [ 'i' => 2 ] ], __METHOD__ );
+
+               // Normal INSERT IGNORE
+               $this->db->begin( __METHOD__ );
+               $this->db->insert(
+                       'foo', [ [ 'i' => 3 ], [ 'i' => 2 ], [ 'i' => 5 ] ], __METHOD__, [ 'IGNORE' ]
+               );
+               $this->assertSame( 2, $this->db->affectedRows() );
+               $this->assertSame(
+                       [ '1', '2', '3', '5' ],
+                       $this->db->selectFieldValues( 'foo', 'i', [], __METHOD__, [ 'ORDER BY' => 'i' ] )
+               );
+               $this->db->rollback( __METHOD__ );
+
+               // INSERT IGNORE doesn't ignore stuff like NOT NULL violations
+               $this->db->begin( __METHOD__ );
+               $this->db->startAtomic( __METHOD__, IDatabase::ATOMIC_CANCELABLE );
+               try {
+                       $this->db->insert(
+                               'foo', [ [ 'i' => 7 ], [ 'i' => null ] ], __METHOD__, [ 'IGNORE' ]
+                       );
+                       $this->db->endAtomic( __METHOD__ );
+                       $this->fail( 'Expected exception not thrown' );
+               } catch ( DBQueryError $e ) {
+                       $this->assertSame( 0, $this->db->affectedRows() );
+                       $this->db->cancelAtomic( __METHOD__ );
+               }
+               $this->assertSame(
+                       [ '1', '2' ],
+                       $this->db->selectFieldValues( 'foo', 'i', [], __METHOD__, [ 'ORDER BY' => 'i' ] )
+               );
+               $this->db->rollback( __METHOD__ );
+       }
+
+       /**
+        * @covers Wikimedia\Rdbms\DatabasePostgres::insert
+        */
+       public function testInsertIgnoreOld() {
+               if ( !$this->db instanceof DatabasePostgres ) {
+                       $this->markTestSkipped( 'Not PostgreSQL' );
+               }
+               if ( $this->db->getServerVersion() < 9.5 ) {
+                       $this->doTestInsertIgnore();
+               } else {
+                       // Hack version to make it take the old code path
+                       $w = TestingAccessWrapper::newFromObject( $this->db );
+                       $oldVer = $w->numericVersion;
+                       $w->numericVersion = 9.4;
+                       try {
+                               $this->doTestInsertIgnore();
+                       } finally {
+                               $w->numericVersion = $oldVer;
+                       }
+               }
+       }
+
+       /**
+        * @covers Wikimedia\Rdbms\DatabasePostgres::insert
+        */
+       public function testInsertIgnoreNew() {
+               if ( !$this->db instanceof DatabasePostgres ) {
+                       $this->markTestSkipped( 'Not PostgreSQL' );
+               }
+               if ( $this->db->getServerVersion() < 9.5 ) {
+                       $this->markTestSkipped( 'PostgreSQL version is ' . $this->db->getServerVersion() );
+               }
+
+               $this->doTestInsertIgnore();
+       }
+
+       private function doTestInsertSelectIgnore() {
+               $reset = new ScopedCallback( function () {
+                       if ( $this->db->explicitTrxActive() ) {
+                               $this->db->rollback( __METHOD__ );
+                       }
+                       $this->db->query( 'DROP TABLE IF EXISTS ' . $this->db->tableName( 'foo' ) );
+                       $this->db->query( 'DROP TABLE IF EXISTS ' . $this->db->tableName( 'bar' ) );
+               } );
+
+               $this->db->query(
+                       "CREATE TEMPORARY TABLE {$this->db->tableName( 'foo' )} (i INTEGER)"
+               );
+               $this->db->query(
+                       "CREATE TEMPORARY TABLE {$this->db->tableName( 'bar' )} (i INTEGER NOT NULL PRIMARY KEY)"
+               );
+               $this->db->insert( 'bar', [ [ 'i' => 1 ], [ 'i' => 2 ] ], __METHOD__ );
+
+               // Normal INSERT IGNORE
+               $this->db->begin( __METHOD__ );
+               $this->db->insert( 'foo', [ [ 'i' => 3 ], [ 'i' => 2 ], [ 'i' => 5 ] ], __METHOD__ );
+               $this->db->insertSelect( 'bar', 'foo', [ 'i' => 'i' ], [], __METHOD__, [ 'IGNORE' ] );
+               $this->assertSame( 2, $this->db->affectedRows() );
+               $this->assertSame(
+                       [ '1', '2', '3', '5' ],
+                       $this->db->selectFieldValues( 'bar', 'i', [], __METHOD__, [ 'ORDER BY' => 'i' ] )
+               );
+               $this->db->rollback( __METHOD__ );
+
+               // INSERT IGNORE doesn't ignore stuff like NOT NULL violations
+               $this->db->begin( __METHOD__ );
+               $this->db->insert( 'foo', [ [ 'i' => 7 ], [ 'i' => null ] ], __METHOD__ );
+               $this->db->startAtomic( __METHOD__, IDatabase::ATOMIC_CANCELABLE );
+               try {
+                       $this->db->insertSelect( 'bar', 'foo', [ 'i' => 'i' ], [], __METHOD__, [ 'IGNORE' ] );
+                       $this->db->endAtomic( __METHOD__ );
+                       $this->fail( 'Expected exception not thrown' );
+               } catch ( DBQueryError $e ) {
+                       $this->assertSame( 0, $this->db->affectedRows() );
+                       $this->db->cancelAtomic( __METHOD__ );
+               }
+               $this->assertSame(
+                       [ '1', '2' ],
+                       $this->db->selectFieldValues( 'bar', 'i', [], __METHOD__, [ 'ORDER BY' => 'i' ] )
+               );
+               $this->db->rollback( __METHOD__ );
+       }
+
+       /**
+        * @covers Wikimedia\Rdbms\DatabasePostgres::nativeInsertSelect
+        */
+       public function testInsertSelectIgnoreOld() {
+               if ( !$this->db instanceof DatabasePostgres ) {
+                       $this->markTestSkipped( 'Not PostgreSQL' );
+               }
+               if ( $this->db->getServerVersion() < 9.5 ) {
+                       $this->doTestInsertSelectIgnore();
+               } else {
+                       // Hack version to make it take the old code path
+                       $w = TestingAccessWrapper::newFromObject( $this->db );
+                       $oldVer = $w->numericVersion;
+                       $w->numericVersion = 9.4;
+                       try {
+                               $this->doTestInsertSelectIgnore();
+                       } finally {
+                               $w->numericVersion = $oldVer;
+                       }
+               }
+       }
+
+       /**
+        * @covers Wikimedia\Rdbms\DatabasePostgres::nativeInsertSelect
+        */
+       public function testInsertSelectIgnoreNew() {
+               if ( !$this->db instanceof DatabasePostgres ) {
+                       $this->markTestSkipped( 'Not PostgreSQL' );
+               }
+               if ( $this->db->getServerVersion() < 9.5 ) {
+                       $this->markTestSkipped( 'PostgreSQL version is ' . $this->db->getServerVersion() );
+               }
+
+               $this->doTestInsertSelectIgnore();
+       }
+
+}
index 854479d..36254f7 100644 (file)
@@ -26,6 +26,11 @@ class DatabaseTestHelper extends Database {
        /** @var array List of row arrays */
        protected $nextResult = [];
 
+       /** @var array|null */
+       protected $nextError = null;
+       /** @var array|null */
+       protected $lastError = null;
+
        /**
         * Array of tables to be considered as existing by tableExist()
         * Use setExistingTables() to alter.
@@ -75,6 +80,16 @@ class DatabaseTestHelper extends Database {
                $this->nextResult = $res;
        }
 
+       /**
+        * @param int $errno Error number
+        * @param string $error Error text
+        * @param array $options
+        *  - wasKnownStatementRollbackError: Return value for wasKnownStatementRollbackError()
+        */
+       public function forceNextQueryError( $errno, $error, $options = [] ) {
+               $this->nextError = [ 'errno' => $errno, 'error' => $error ] + $options;
+       }
+
        protected function addSql( $sql ) {
                // clean up spaces before and after some words and the whole string
                $this->lastSqls[] = trim( preg_replace(
@@ -88,7 +103,13 @@ class DatabaseTestHelper extends Database {
                        return; // no $fname parameter
                }
 
-               if ( substr( $fname, 0, strlen( $this->testName ) ) !== $this->testName ) {
+               // Handle some internal calls from the Database class
+               $check = $fname;
+               if ( preg_match( '/^Wikimedia\\\\Rdbms\\\\Database::query \((.+)\)$/', $fname, $m ) ) {
+                       $check = $m[1];
+               }
+
+               if ( substr( $check, 0, strlen( $this->testName ) ) !== $this->testName ) {
                        throw new MWException( 'function name does not start with test class. ' .
                                $fname . ' vs. ' . $this->testName . '. ' .
                                'Please provide __METHOD__ to database methods.' );
@@ -107,7 +128,6 @@ class DatabaseTestHelper extends Database {
 
        public function query( $sql, $fname = '', $tempIgnore = false ) {
                $this->checkFunctionName( $fname );
-               $this->addSql( $sql );
 
                return parent::query( $sql, $fname, $tempIgnore );
        }
@@ -167,11 +187,17 @@ class DatabaseTestHelper extends Database {
        }
 
        function lastErrno() {
-               return -1;
+               return $this->lastError ? $this->lastError['errno'] : -1;
        }
 
        function lastError() {
-               return 'test';
+               return $this->lastError ? $this->lastError['error'] : 'test';
+       }
+
+       protected function wasKnownStatementRollbackError() {
+               return isset( $this->lastError['wasKnownStatementRollbackError'] )
+                       ? $this->lastError['wasKnownStatementRollbackError']
+                       : false;
        }
 
        function fieldInfo( $table, $field ) {
@@ -212,8 +238,18 @@ class DatabaseTestHelper extends Database {
        }
 
        protected function doQuery( $sql ) {
+               $sql = preg_replace( '< /\* .+?  \*/>', '', $sql );
+               $this->addSql( $sql );
+
+               if ( $this->nextError ) {
+                       $this->lastError = $this->nextError;
+                       $this->nextError = null;
+                       return false;
+               }
+
                $res = $this->nextResult;
                $this->nextResult = [];
+               $this->lastError = null;
 
                return new FakeResultWrapper( $res );
        }
index 85b8c62..7bd1611 100644 (file)
@@ -22,7 +22,11 @@ class SamplingStatsdClientTest extends PHPUnit\Framework\TestCase {
                } else {
                        $sender->expects( $this->never() )->method( 'write' );
                }
-               mt_srand( $seed );
+               if ( defined( 'MT_RAND_PHP' ) ) {
+                       mt_srand( $seed, MT_RAND_PHP );
+               } else {
+                       mt_srand( $seed );
+               }
                $client = new SamplingStatsdClient( $sender );
                $client->send( $data, $sampleRate );
        }
index 5a06cc7..40e07d8 100644 (file)
@@ -4,6 +4,7 @@ use Wikimedia\Rdbms\IDatabase;
 use Wikimedia\Rdbms\LikeMatch;
 use Wikimedia\Rdbms\Database;
 use Wikimedia\TestingAccessWrapper;
+use Wikimedia\Rdbms\DBTransactionStateError;
 use Wikimedia\Rdbms\DBUnexpectedError;
 
 /**
@@ -1540,6 +1541,44 @@ class DatabaseSQLTest extends PHPUnit\Framework\TestCase {
                $this->assertEquals( 0, $this->database->trxLevel() );
        }
 
+       /**
+        * @covers \Wikimedia\Rdbms\Database::query
+        */
+       public function testImplicitTransactionRollback() {
+               $doError = function ( $wasKnown = true ) {
+                       $this->database->forceNextQueryError( 666, 'Evilness' );
+                       try {
+                               $this->database->delete( 'error', '1', __CLASS__ . '::SomeCaller' );
+                               $this->fail( 'Expected exception not thrown' );
+                       } catch ( DBError $e ) {
+                               $this->assertSame( 666, $e->errno );
+                       }
+               };
+
+               $this->database->setFlag( Database::DBO_TRX );
+
+               // Implicit transaction gets silently rolled back
+               $this->database->begin( __METHOD__, Database::TRANSACTION_INTERNAL );
+               call_user_func( $doError, false );
+               $this->database->delete( 'x', [ 'field' => 1 ], __METHOD__ );
+               $this->database->commit( __METHOD__, Database::FLUSHING_INTERNAL );
+               // phpcs:ignore
+               $this->assertLastSql( 'BEGIN; DELETE FROM error WHERE 1; ROLLBACK; BEGIN; DELETE FROM x WHERE field = \'1\'; COMMIT' );
+
+               // ... unless there were prior writes
+               $this->database->begin( __METHOD__, Database::TRANSACTION_INTERNAL );
+               $this->database->delete( 'x', [ 'field' => 1 ], __METHOD__ );
+               call_user_func( $doError, false );
+               try {
+                       $this->database->delete( 'x', [ 'field' => 1 ], __METHOD__ );
+                       $this->fail( 'Expected exception not thrown' );
+               } catch ( DBTransactionStateError $e ) {
+               }
+               $this->database->rollback( __METHOD__, Database::FLUSHING_INTERNAL );
+               // phpcs:ignore
+               $this->assertLastSql( 'BEGIN; DELETE FROM x WHERE field = \'1\'; DELETE FROM error WHERE 1; ROLLBACK' );
+       }
+
        /**
         * @covers \Wikimedia\Rdbms\Database::close
         */